import { useState } from "react";

import type { PuzzleType } from "../lib/api";

type Props = {
  puzzleType: PuzzleType;
  problemAscii: string;
  boardAscii: string;
  onChange: (nextAscii: string) => void;
};

type BridgesOpportunity = {
  id: string;
  kind: "horizontal" | "vertical";
  cells: Array<[number, number]>;
};

const FLOW_FREE_COLORS = [
  "#f15b5d",
  "#f5a623",
  "#f6df5a",
  "#7ed957",
  "#36cfc9",
  "#4b8ef7",
  "#7a5cff",
  "#ff8bd1",
  "#7d5a50",
  "#5f6a6b",
];

function splitLines(board: string): string[] {
  return board.replace(/\r/g, "").split("\n");
}

function cloneGrid(lines: string[]): string[][] {
  return lines.map((line) => [...line]);
}

function joinGrid(grid: string[][]): string {
  return grid.map((row) => row.join("")).join("\n");
}

function isClueChar(char: string): boolean {
  return /[0-9A-G]/.test(char);
}

function parseBridges(problemAscii: string) {
  const lines = splitLines(problemAscii);
  const opportunities: BridgesOpportunity[] = [];
  const cellToOpportunity = new Map<string, BridgesOpportunity>();
  for (let r = 0; r < lines.length; r += 1) {
    for (let c = 0; c < lines[r].length; c += 1) {
      if (!isClueChar(lines[r][c])) {
        continue;
      }
      let next = c + 1;
      while (next < lines[r].length && lines[r][next] === ".") {
        next += 1;
      }
      if (next < lines[r].length && isClueChar(lines[r][next]) && next > c + 1) {
        const opp: BridgesOpportunity = {
          id: `h-${r}-${c}-${next}`,
          kind: "horizontal",
          cells: [],
        };
        for (let cell = c + 1; cell < next; cell += 1) {
          opp.cells.push([r, cell]);
          cellToOpportunity.set(`${r}:${cell}`, opp);
        }
        opportunities.push(opp);
      }
      let nextRow = r + 1;
      while (nextRow < lines.length && c < lines[nextRow].length && lines[nextRow][c] === ".") {
        nextRow += 1;
      }
      if (
        nextRow < lines.length &&
        c < lines[nextRow].length &&
        isClueChar(lines[nextRow][c]) &&
        nextRow > r + 1
      ) {
        const opp: BridgesOpportunity = {
          id: `v-${r}-${c}-${nextRow}`,
          kind: "vertical",
          cells: [],
        };
        for (let cell = r + 1; cell < nextRow; cell += 1) {
          opp.cells.push([cell, c]);
          cellToOpportunity.set(`${cell}:${c}`, opp);
        }
        opportunities.push(opp);
      }
    }
  }
  return { lines, opportunities, cellToOpportunity };
}

function bridgesBoardFromStates(
  problemAscii: string,
  states: Record<string, 0 | 1 | 2>,
): string {
  const parsed = parseBridges(problemAscii);
  const grid = cloneGrid(parsed.lines);
  for (const opportunity of parsed.opportunities) {
    const level = states[opportunity.id] ?? 0;
    const symbol =
      opportunity.kind === "horizontal"
        ? level === 2
          ? "="
          : level === 1
            ? "-"
            : "."
        : level === 2
          ? '"'
          : level === 1
            ? "|"
            : ".";
    for (const [r, c] of opportunity.cells) {
      grid[r][c] = symbol;
    }
  }
  return joinGrid(grid);
}

function bridgesStatesFromBoard(
  problemAscii: string,
  boardAscii: string,
): Record<string, 0 | 1 | 2> {
  const parsed = parseBridges(problemAscii);
  const boardLines = splitLines(boardAscii);
  const states: Record<string, 0 | 1 | 2> = {};
  for (const opportunity of parsed.opportunities) {
    const [r, c] = opportunity.cells[0];
    const char = boardLines[r]?.[c] ?? ".";
    if (opportunity.kind === "horizontal") {
      states[opportunity.id] = char === "=" ? 2 : char === "-" ? 1 : 0;
    } else {
      states[opportunity.id] = char === '"' ? 2 : char === "|" ? 1 : 0;
    }
  }
  return states;
}

function BridgesEditor({ problemAscii, boardAscii, onChange }: Omit<Props, "puzzleType">) {
  const parsed = parseBridges(problemAscii);
  const states = bridgesStatesFromBoard(problemAscii, boardAscii);

  function cycle(opportunity: BridgesOpportunity) {
    const nextStates = { ...states };
    const current = nextStates[opportunity.id] ?? 0;
    nextStates[opportunity.id] = (((current + 1) % 3) as 0 | 1 | 2);
    onChange(bridgesBoardFromStates(problemAscii, nextStates));
  }

  return (
    <div
      className="board-grid"
      style={{ gridTemplateColumns: `repeat(${parsed.lines[0]?.length ?? 0}, 38px)` }}
    >
      {parsed.lines.flatMap((line, r) =>
        [...line].map((char, c) => {
          const opportunity = parsed.cellToOpportunity.get(`${r}:${c}`);
          const boardChar = splitLines(boardAscii)[r]?.[c] ?? char;
          const className = isClueChar(char)
            ? "board-cell fixed"
            : opportunity
              ? "board-cell"
              : "board-cell water";
          return (
            <button
              key={`${r}-${c}`}
              type="button"
              className={className}
              onClick={() => opportunity && cycle(opportunity)}
              disabled={!opportunity}
            >
              {isClueChar(char) ? char : boardChar === "." ? "" : boardChar}
            </button>
          );
        }),
      )}
    </div>
  );
}

function FlowFreeEditor({ problemAscii, boardAscii, onChange }: Omit<Props, "puzzleType">) {
  const problemLines = splitLines(problemAscii);
  const boardLines = splitLines(boardAscii);
  const letters = [...new Set(problemAscii.replace(/[^A-Z]/g, "").split(""))].sort();
  const [activeColor, setActiveColor] = useState(letters[0] ?? "A");

  function updateCell(r: number, c: number) {
    if (problemLines[r][c] !== ".") {
      setActiveColor(problemLines[r][c]);
      return;
    }
    const grid = cloneGrid(boardLines);
    grid[r][c] = grid[r][c] === activeColor ? "." : activeColor;
    onChange(joinGrid(grid));
  }

  function swatchColor(letter: string) {
    return FLOW_FREE_COLORS[(letter.charCodeAt(0) - 65) % FLOW_FREE_COLORS.length];
  }

  return (
    <div className="pattern-layout">
      <div className="palette">
        {letters.map((letter) => (
          <button
            key={letter}
            type="button"
            className={activeColor === letter ? "active" : ""}
            style={{ background: swatchColor(letter) }}
            onClick={() => setActiveColor(letter)}
          >
            {letter}
          </button>
        ))}
      </div>
      <div
        className="board-grid"
        style={{ gridTemplateColumns: `repeat(${problemLines[0]?.length ?? 0}, 38px)` }}
      >
        {boardLines.flatMap((line, r) =>
          [...line].map((char, c) => {
            const fixed = problemLines[r][c] !== ".";
            const letter = fixed ? problemLines[r][c] : char;
            return (
              <button
                key={`${r}-${c}`}
                type="button"
                className={`board-cell ${fixed ? "fixed" : "water"}`}
                style={{
                  background:
                    letter === "."
                      ? undefined
                      : `${swatchColor(letter)}cc`,
                }}
                onClick={() => updateCell(r, c)}
              >
                {letter === "." ? "" : letter}
              </button>
            );
          }),
        )}
      </div>
    </div>
  );
}

function CharGridEditor({
  problemAscii,
  boardAscii,
  onChange,
  canEdit,
  nextChar,
}: {
  problemAscii: string;
  boardAscii: string;
  onChange: (nextAscii: string) => void;
  canEdit: (r: number, c: number, char: string) => boolean;
  nextChar: (r: number, c: number, current: string, problemChar: string) => string;
}) {
  const problemLines = splitLines(problemAscii);
  const boardLines = splitLines(boardAscii);
  const width = Math.max(...boardLines.map((line) => line.length));
  function updateCell(r: number, c: number) {
    const current = boardLines[r]?.[c] ?? " ";
    const problemChar = problemLines[r]?.[c] ?? " ";
    const grid = boardLines.map((line) => line.padEnd(width, " ").split(""));
    grid[r][c] = nextChar(r, c, current, problemChar);
    onChange(joinGrid(grid).replace(/\s+$/g, "").replace(/\n\s+$/g, "\n"));
  }
  return (
    <div
      className="char-board"
      style={{ gridTemplateColumns: `repeat(${width}, 24px)` }}
    >
      {Array.from({ length: boardLines.length }).flatMap((_, r) =>
        Array.from({ length: width }).map((__, c) => {
          const problemChar = problemLines[r]?.[c] ?? " ";
          const current = boardLines[r]?.[c] ?? " ";
          const editable = canEdit(r, c, problemChar);
          return (
            <button
              key={`${r}-${c}`}
              type="button"
              className={`board-char ${editable ? "clickable" : ""}`}
              disabled={!editable}
              onClick={() => updateCell(r, c)}
            >
              {current === " " ? "\u00A0" : current}
            </button>
          );
        }),
      )}
    </div>
  );
}

function LoopyEditor({ problemAscii, boardAscii, onChange }: Omit<Props, "puzzleType">) {
  return (
    <CharGridEditor
      problemAscii={problemAscii}
      boardAscii={boardAscii}
      onChange={onChange}
      canEdit={(r, c, char) => {
        if (char !== " ") {
          return false;
        }
        const isHorizontal = r > 0 && r < splitLines(problemAscii).length - 1 && r % 2 === 1 && c % 2 === 0;
        const isVertical = r > 0 && r < splitLines(problemAscii).length - 1 && r % 2 === 0 && c % 2 === 1;
        return isHorizontal || isVertical;
      }}
      nextChar={(r, c, current, problemChar) => {
        if (problemChar !== " ") {
          return current;
        }
        const isHorizontal = r % 2 === 1 && c % 2 === 0;
        if (isHorizontal) {
          return current === " " ? "-" : current === "-" ? "x" : " ";
        }
        return current === " " ? "|" : current === "|" ? "x" : " ";
      }}
    />
  );
}

function GalaxiesEditor({ problemAscii, boardAscii, onChange }: Omit<Props, "puzzleType">) {
  return (
    <CharGridEditor
      problemAscii={problemAscii}
      boardAscii={boardAscii}
      onChange={onChange}
      canEdit={(r, c, char) => {
        if (char !== " ") {
          return false;
        }
        const isHorizontal = r % 2 === 0 && c % 2 === 1;
        const isVertical = r % 2 === 1 && c % 2 === 0;
        return isHorizontal || isVertical;
      }}
      nextChar={(r, _c, current, _problemChar) => {
        const isHorizontal = r % 2 === 0;
        if (isHorizontal) {
          return current === "-" ? " " : "-";
        }
        return current === "|" ? " " : "|";
      }}
    />
  );
}

function parsePattern(boardAscii: string) {
  const lines = splitLines(boardAscii);
  const firstContentIndex = lines.findIndex((line) => line.includes("|"));
  const topLines = lines.slice(0, firstContentIndex);
  const rows: Array<{ prefix: string; cells: string[]; border: string }> = [];
  for (let idx = firstContentIndex; idx < lines.length; idx += 2) {
    const content = lines[idx];
    const border = lines[idx + 1];
    if (!content || !border) {
      break;
    }
    const firstBar = content.indexOf("|");
    const prefix = content.slice(0, firstBar);
    const parts = content.slice(firstBar).split("|").slice(1, -1);
    rows.push({ prefix, cells: parts, border });
  }
  return { topLines, rows };
}

function patternToAscii(template: ReturnType<typeof parsePattern>): string {
  const lines = [...template.topLines];
  for (const row of template.rows) {
    lines.push(`${row.prefix}|${row.cells.join("|")}|`);
    lines.push(row.border);
  }
  return lines.join("\n");
}

function PatternEditor({ boardAscii, onChange }: Omit<Props, "puzzleType" | "problemAscii"> & { problemAscii: string }) {
  const template = parsePattern(boardAscii);

  function cycle(rowIndex: number, cellIndex: number) {
    const next = parsePattern(boardAscii);
    const current = next.rows[rowIndex].cells[cellIndex];
    next.rows[rowIndex].cells[cellIndex] =
      current === "  " ? "##" : current === "##" ? ".." : "  ";
    onChange(patternToAscii(next));
  }

  return (
    <div className="pattern-layout">
      <pre className="ascii-preview">{template.topLines.join("\n")}</pre>
      <div className="pattern-grid">
        {template.rows.map((row, rowIndex) => (
          <div key={rowIndex} className="pattern-row">
            <div className="pattern-clue">{row.prefix.trim()}</div>
            <div className="board-grid" style={{ gridTemplateColumns: `repeat(${row.cells.length}, 44px)` }}>
              {row.cells.map((cell, cellIndex) => (
                <button
                  key={`${rowIndex}-${cellIndex}`}
                  type="button"
                  className={`board-square ${cell === "##" ? "fill" : cell === ".." ? "empty" : ""}`}
                  onClick={() => cycle(rowIndex, cellIndex)}
                >
                  {cell === "  " ? "" : cell}
                </button>
              ))}
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

function parseUndead(boardAscii: string) {
  const lines = splitLines(boardAscii);
  const header = lines[0] ?? "";
  const topLine = lines[2] ?? "";
  const bottomLine = lines.at(-1) ?? "";
  const rows = lines.slice(3, -1).map((line) => {
    const tokens = line.trim().split(/\s+/);
    return {
      left: tokens[0] ?? "",
      cells: tokens.slice(1, -1),
      right: tokens.at(-1) ?? "",
    };
  });
  return { header, topLine, bottomLine, rows };
}

function undeadToAscii(template: ReturnType<typeof parseUndead>): string {
  const lines = [template.header, "", template.topLine];
  for (const row of template.rows) {
    lines.push(` ${row.left} ${row.cells.join(" ")} ${row.right}`);
  }
  lines.push(template.bottomLine);
  return lines.join("\n");
}

function UndeadEditor({ boardAscii, onChange }: Omit<Props, "puzzleType" | "problemAscii"> & { problemAscii: string }) {
  const template = parseUndead(boardAscii);
  function cycle(rowIndex: number, cellIndex: number) {
    const current = template.rows[rowIndex].cells[cellIndex];
    if (current === "/" || current === "\\") {
      return;
    }
    const next = parseUndead(boardAscii);
    next.rows[rowIndex].cells[cellIndex] =
      current === "." ? "G" : current === "G" ? "V" : current === "V" ? "Z" : ".";
    onChange(undeadToAscii(next));
  }

  return (
    <div className="pattern-layout">
      <pre className="ascii-preview">{`${template.header}\n\n${template.topLine}\n${template.bottomLine}`}</pre>
      <div className="board-grid" style={{ gridTemplateColumns: `repeat(${template.rows[0]?.cells.length ?? 0}, 38px)` }}>
        {template.rows.flatMap((row, rowIndex) =>
          row.cells.map((cell, cellIndex) => {
            const fixedMirror = cell === "/" || cell === "\\";
            return (
              <button
                key={`${rowIndex}-${cellIndex}`}
                type="button"
                className={`board-cell ${fixedMirror ? "mirror" : ""}`}
                onClick={() => cycle(rowIndex, cellIndex)}
              >
                {cell === "." ? "" : cell}
              </button>
            );
          }),
        )}
      </div>
    </div>
  );
}

export function PuzzleEditor(props: Props) {
  if (props.puzzleType === "bridges") {
    return <BridgesEditor {...props} />;
  }
  if (props.puzzleType === "flow_free") {
    return <FlowFreeEditor {...props} />;
  }
  if (props.puzzleType === "galaxies") {
    return <GalaxiesEditor {...props} />;
  }
  if (props.puzzleType === "loopy") {
    return <LoopyEditor {...props} />;
  }
  if (props.puzzleType === "pattern") {
    return <PatternEditor {...props} />;
  }
  if (props.puzzleType === "undead") {
    return <UndeadEditor {...props} />;
  }
  return null;
}
