type LightDir = 1 | 2 | 3 | 4;

export const UP = 1 satisfies LightDir;
export const DOWN = 2 satisfies LightDir;
export const LEFT = 3 satisfies LightDir;
export const RIGHT = 4 satisfies LightDir;

const DirTable = {
  [UP]: [0, -1],
  [DOWN]: [0, 1],
  [LEFT]: [-1, 0],
  [RIGHT]: [1, 0],
} satisfies Record<LightDir, [number, number]>;

type LightDirSet = Set<LightDir>;
type MapType = "." | "/" | "\\" | "|" | "-";
type MapInfo = {
  type: MapType;
  lightTransportation: LightDirSet;
};

export function toLightMap(x: string): MapInfo[][] {
  return x.split("\n").map(x => x.trim().split("").map(x => {
    return {
      type: x as MapType,
      lightTransportation: new Set(),
    } as MapInfo;
  }));
}

export function toString(x: MapInfo[][]): string {
  return x.map(x => x.map(y => {
    if (y.type !== ".") {
      return y.type;
    }
    if (y.lightTransportation.size === 0) {
      return ".";
    }
    if (y.lightTransportation.size > 1) {
      return y.lightTransportation.size.toString();
    }
    const dir = y.lightTransportation.values().next().value;
    switch (dir) {
      case UP:
        return "^";
      case DOWN:
        return "v";
      case LEFT:
        return "<";
      case RIGHT:
        return ">";
    }
    throw new Error(`unreachable! unknown dir ${dir}`);
  }).join("")).join("\n");
}

function nextDirection(type: MapType, direction: LightDir): LightDirSet | LightDir {
  switch (type) {
    case ".":
      return direction;
    case "/":
      return ({
        [UP]: RIGHT,
        [RIGHT]: UP,
        [DOWN]: LEFT,
        [LEFT]: DOWN
      } satisfies Record<LightDir, LightDir>)[direction];
    case "\\":
      return ({
        [UP]: LEFT,
        [RIGHT]: DOWN,
        [DOWN]: RIGHT,
        [LEFT]: UP
      } satisfies Record<LightDir, LightDir>)[direction];
    // light spliter
    case "|":
      {
        if (direction === UP || direction === DOWN) {
          return direction;
        }
        else {
          return new Set([UP, DOWN]);
        }
      }
    // light splitter
    case "-":
      {
        if (direction === LEFT || direction === RIGHT) {
          return direction;
        }
        else {
          return new Set([LEFT, RIGHT]);
        }
      }
    default:
      throw new Error(`unreachable! unknown type ${type}`);
  }
}

export function lightTrasport(map: MapInfo[][], [x, y]: [number, number], direction: LightDir) {
  const [width, height] = [map[0].length, map.length];
  for (; ;) {
    if (x < 0 || x >= width || y < 0 || y >= height) {
      return;
    }

    const info = map[y][x];

    // if light is already on
    if (info.lightTransportation.has(direction)) {
      return;
    }
    // mark light direction
    info.lightTransportation.add(direction);

    // move to next position by direction
    const nextDir = nextDirection(info.type, direction);
    // if light is splited, go to each direction
    if (nextDir instanceof Set) {
      nextDir.forEach(dir => lightTrasport(map, [x, y], dir));
      return;
    }
    // if light is not splited, go to next position
    const dirVec = DirTable[nextDir as keyof typeof DirTable];
    [x, y] = [x + dirVec[0], y + dirVec[1]];
    direction = nextDir;
  }
}

export function countPoweredTile(lightMap : MapInfo[][]){
  let count = 0
  lightMap.forEach(x => {
    x.forEach(y => {
      if (y.lightTransportation.size > 0) {
        count += 1
      }
    })
  })
  return count
}

export function clearLight(lightMap : MapInfo[][]){
  lightMap.forEach(x => {
    x.forEach(y => {
      y.lightTransportation.clear();
    })
  })
}