# Revamping the libipuz edit APIs
**Status:** Reviewed *federico@gnome.org* | partially-implemented

## Summary

Currently, all editing is split between `xword-state.c` and
libipuz. We propose a structured way to handle editing to make it
easier to reason about, and to put the majority of the complexity in
libipuz.

Currently, the `fix()` API is implemented, but the linters are not.

## Problem Statement
There currently exists a mixture of API levels used to mutate puzzles
through the editor. For example, it's possible to change an individual
cell within the puzzle by calling `ipuz_cell_set_*`, or
`ipuz_style_set_*`. Similarly, you can change a clue. However, all
these functions bring some challenges with them, that can lead to an
internally inconsistent state:

* Changing the cell_type can leave the numbering and clue cells
  inconsistent.
* Changing the symmetry can be super destructive
* Even clues are super-tricky: We need to make sure every cell knows
  which clue it's part, and vice-versa.

Additionally, there are some conventions we want to enforce that
aren't part of the ipuz spec. In particular:

* For crosswords, there are only across/down clues, and every cell is
  part of at least one clue.
* We are correctly numbered. Clue numbers increase by 1, and there are no gaps
* We store the clue cells within the puzzle, and they don't have
  discontinuities

We tried to work around this in calls from `xword-state.c` by
introducing the `ipuz_crossword_set_cell_type()` function, as well as
storing the symmetry within the puzzle. This is always immediatedly
followed but a call to `ipuz_crossword_calculate_clues()`. This level
of granularity isn't sufficient for what we need to do and hasn't aged
well.

## Challenges

### Subtypes

Arrowword, Filippine, and Barred puzzles inherit from Crossword, and
Acrostics don't inherit from Crosswords at all. They don't share the
same conventions that Crossword has, though there are some
commonalitites. All of these will need their own adjustment to
editing.

For example, Filippine puzzles only have across clues, and have a
vertical line that's highlighted with the answer. Barred puzzles don't
have blocks or nulls, and expect "T", "L", and "TL" styles to
exist. Arrowwords need to have the arrow updated. Symmetry makes
almost no sense for Acrostics.

:::{note}
In addition, both Filippine and Acrostic puzzles will
benefit from a locked-down custom editor that doesn't even let you
change the grid directly. They might want to bypass XwordState's
editing system entirely. Given that we called it `XwordState`, the
introduction of an `AcrosticState` wouldn't be surprising, but this
may be substantial work and sources of errors.
:::

### Raw Mode

Some puzzles do need to break the rules. There are fun puzzles with
diagonal clues, or 'J' hooks that don't reveal themselves
immediately. Maybe you do want a barred puzzle with blocks and/or
discontinuities. We may want to consider adding a *raw* mode to the
editor that just uses the `_set()` functions and lets you produce
valid-but-unusual puzzles. This mode would require people to
automatically set the range of cells of clues, for example.

## Proposal

### libipuz changes
* We will have three different APIs for libipuz. First the existing one
demarked by the ipuz `_set()` functions. These will let you manipulate
the puzzle however you see fit. Example: `ipuz_cell_set_label()`
* We add a set of linting APIs demarked by `_check()` functions. This
will return a list of potentially broken attributes that might need
fixing. We will have to define a set of types for each check
type. Example: `ipuz_crossword_check_cell_labels()`.
* We add a correction API, demarked by `_fix()` functions
    * `_fix()` functions may need arguments. For example,
      `fix_symmetry()` will need to take in a list of modified cells
      to update. Otherwise, we won't know which direction to change
      things.
* As a convenience, we add an `ipuz_crossword_fix_all()` function to
  call the fix functions in the correct order.
* We remove symmetry as a tracked feature of crosswords.

Here's a diagram of this behavior.

![libipuz editing changes overview](ipuz-edit-interface-graph.jpg)<br>
_sketch of editing changes_

:::{note}
We will make both the `_check()` and `_fix()` function
class methods, so that child types can override or chain to
them. Most notably, `fix_all()` also needs to be a
class_method. `fix_all()` will also need to take in arguments, so
the class method will require kwargs.
:::

### XwordState changes
* We move any settings to give us hints about the type of fixes to the
  Quirks object.
    * We will introduce a *mangaged_mode*/*raw_mode* enum to the
      Quirks object to track whether we want to fix things up
      automatically or not.
    * As another example, *symmetry* is another quirk setting
* All current mutators within xword_state will fix up puzzle based on
  that mode.

### Editor Changes / Raw Mode

* There's a (currently unwritten) style tab in the editor that will
let you customize the label / style of grids. We will have to prevent
the user from breaking assumptions
* The current `edit-` widgets were written to expect the puzzle type
and base puzzle to change. We will make it so that `PUZZLE_KIND` is a
construct-only property. `raw_mode` should be a property that can
change, and the widgets have to adjust themselves to adapt.
