import { CountMap } from "util/count-map";
import { CharColl, Collection2D } from "util/collection";

// If blank strings/ints is not enough
export const inputMapper = (inputs: string) => CharColl(inputs);

function isNumber(char: string | undefined): boolean {
  return !!char && char.length === 1 && "1234567890".includes(char);
}

function isSymbol(char: string): boolean {
  return char !== "." && !isNumber(char);
}

export function solution1(inputs: Collection2D<string>): number {
  const grid = inputs.x.toGrid();

  let result = 0;

  for (const [y, row] of grid.traverseRows()) {
    const iter = row.entries();

    for (const [x, char] of iter) {
      if (!isNumber(char)) continue;

      const threeDigits = row.slice(x, x + 3);
      const numLength = threeDigits.countWhile(isNumber);

      if (numLength >= 1) {
        for (let i = x; i < x + numLength; i++) {
          if (grid.adjacent(i, y).some(([val]) => isSymbol(val))) {
            const add = threeDigits.slice(0, numLength).join("");

            result += parseInt(add);

            break;
          }
        }

        // Skip til the end of the number
        for (let i = x; i < x + numLength; i++) {
          iter.next();
        }
      }
    }
  }

  return result;
}

export function solution2(inputs: Collection2D<string>): number {
  const grid = inputs.x.toGrid();

  const gears = new Map<string, number>();
  const counter = new CountMap<string>();

  for (const [y, row] of grid.traverseRows()) {
    const iter = row.entries();

    for (const [x, char] of iter) {
      if (!isNumber(char)) continue;

      const threeDigits = row.slice(x, x + 3);
      const numLength = threeDigits.countWhile(isNumber);

      if (numLength >= 1) {
        for (let i = x; i < x + numLength; i++) {
          const gear = grid.adjacent(i, y).find(([val]) => val === "*");

          if (gear) {
            const [_, gx, gy] = gear;
            const add = Number(threeDigits.slice(0, numLength).join(""));
            const gearName = `${gx},${gy}`;

            gears.set(gearName, (gears.get(gearName) ?? 1) * add);
            counter.add(gearName);

            break;
          }
        }

        // Skip til the end of the number
        for (let i = x; i < x + numLength; i++) {
          iter.next();
        }
      }
    }
  }

  let result = 0;

  for (const [gear, count] of counter.entries()) {
    if (count === 2) {
      result += gears.get(gear) ?? 0;
    }
  }

  return result;
}
