<script setup lang="ts">
import gsap from "gsap";

const { onRender, onClick } = useScreen();

const app = useAppStore();
const store = useSettingsStore();
const { assets } = useAssets();
const confirmationModal = useConfirmationModal();
const achievements = useAchievementsStore();

const GRID_SIZE = 4;
const GRID_START_X = 32;
const GRID_START_Y = 40;
const CELL_SIZE = 16;
const SPACING = 16;
const ANIMATION_SPEED = 475;

const PALETTE_SLIDE_OFFSET = 96;
const PALETTE_SLIDE_DURATION = 0.25;
const CELL_ANIMATION_DURATION = 0.04;
const CELL_ANIMATION_STAGGER = 0.02;

const SNAIL_ORDER = [0, 1, 2, 3, 7, 11, 15, 14, 13, 12, 8, 4, 5, 6, 10, 9];

// animation state
const animation = reactive({
  cellVisibility: Array(16).fill(0) as number[],
  paletteOffsetY: PALETTE_SLIDE_OFFSET,
  paletteOpacity: 0,
  textOpacity: 0,
  isIntro: true,
  isOutro: false,
});
const isAnimating = computed(() => animation.isIntro || animation.isOutro);

const animateIntro = (): gsap.core.Timeline => {
  animation.isIntro = true;

  const timeline = gsap
    .timeline({
      onComplete: () => {
        animation.isIntro = false;
      },
    })
    .to(
      animation,
      { paletteOffsetY: 0, duration: PALETTE_SLIDE_DURATION, ease: "none" },
      0,
    )
    .to(
      animation,
      { paletteOpacity: 1, duration: PALETTE_SLIDE_DURATION, ease: "none" },
      0,
    );

  const colorsDuration =
    SNAIL_ORDER.length * CELL_ANIMATION_STAGGER + CELL_ANIMATION_DURATION;
  timeline.to(
    animation,
    { textOpacity: 1, duration: colorsDuration * 0.5, ease: "none" },
    PALETTE_SLIDE_DURATION + colorsDuration * 0.5,
  );

  for (let i = 0; i < SNAIL_ORDER.length; i++) {
    timeline.to(
      animation.cellVisibility,
      {
        [SNAIL_ORDER[i]!]: 1,
        duration: CELL_ANIMATION_DURATION,
        ease: "none",
      },
      PALETTE_SLIDE_DURATION + i * CELL_ANIMATION_STAGGER,
    );
  }

  return timeline;
};

const animateOutro = (): gsap.core.Timeline => {
  animation.isOutro = true;

  const timeline = gsap.timeline();

  const colorsOutroDuration =
    SNAIL_ORDER.length * CELL_ANIMATION_STAGGER + CELL_ANIMATION_DURATION;
  timeline.to(
    animation,
    { textOpacity: 0, duration: colorsOutroDuration * 0.5, ease: "none" },
    0,
  );

  for (let i = SNAIL_ORDER.length - 1; i >= 0; i--) {
    timeline.to(
      animation.cellVisibility,
      {
        [SNAIL_ORDER[i]!]: 0,
        duration: CELL_ANIMATION_DURATION,
        ease: "none",
      },
      (SNAIL_ORDER.length - 1 - i) * CELL_ANIMATION_STAGGER,
    );
  }

  timeline.to(
    animation,
    {
      paletteOffsetY: PALETTE_SLIDE_OFFSET,
      duration: PALETTE_SLIDE_DURATION,
      ease: "none",
    },
    colorsOutroDuration,
  );
  timeline.to(
    animation,
    { paletteOpacity: 0, duration: PALETTE_SLIDE_DURATION, ease: "none" },
    colorsOutroDuration,
  );

  return timeline;
};

onMounted(() => {
  animateIntro();
});

const originalSelectedCol = app.color.col;
const originalSelectedRow = app.color.row;
const originalColor = app.color.hex;

let selectedCol = app.color.col;
let selectedRow = app.color.row;
let selectorX = GRID_START_X + selectedCol * (CELL_SIZE + SPACING) - 4;
let selectorY = GRID_START_Y + selectedRow * (CELL_SIZE + SPACING) - 4;

const select = (col: number, row: number) => {
  selectedCol = col;
  selectedRow = row;
  app.setColor(col, row);
};

useKeyDown((key) => {
  if (isAnimating.value) return;

  switch (key) {
    case "NDS_UP":
      if (selectedRow > 0) select(selectedCol, selectedRow - 1);
      break;
    case "NDS_RIGHT":
      if (selectedCol < GRID_SIZE - 1) select(selectedCol + 1, selectedRow);
      break;
    case "NDS_DOWN":
      if (selectedRow < GRID_SIZE - 1) select(selectedCol, selectedRow + 1);
      break;
    case "NDS_LEFT":
      if (selectedCol > 0) select(selectedCol - 1, selectedRow);
      break;
  }
});

onClick((x, y) => {
  if (isAnimating.value) return;

  const relativeX = x - GRID_START_X;
  const relativeY = y - GRID_START_Y;

  const col = Math.floor(relativeX / (CELL_SIZE + SPACING));
  const row = Math.floor(relativeY / (CELL_SIZE + SPACING));

  const cellLocalX = relativeX % (CELL_SIZE + SPACING);
  const cellLocalY = relativeY % (CELL_SIZE + SPACING);

  if (
    rectContains([0, 0, GRID_SIZE - 1, GRID_SIZE - 1], [col, row]) &&
    rectContains([0, 0, CELL_SIZE + 1, CELL_SIZE + 1], [cellLocalX, cellLocalY])
  ) {
    select(col, row);
  }
});

onRender((ctx, deltaTime) => {
  ctx.globalAlpha = animation.paletteOpacity;
  ctx.translate(0, animation.paletteOffsetY);

  assets.images.settings.bottomScreen.user.colorPalette.draw(ctx, 16, 32);

  // colors
  for (let row = 0; row < GRID_SIZE; row++) {
    for (let col = 0; col < GRID_SIZE; col++) {
      const cellIndex = row * GRID_SIZE + col;
      const visibility = animation.cellVisibility[cellIndex]!;

      if (visibility > 0) {
        const x = GRID_START_X + col * (CELL_SIZE + SPACING);
        const y = GRID_START_Y + row * (CELL_SIZE + SPACING);
        const size = Math.floor(CELL_SIZE * visibility);
        const offset = Math.floor((CELL_SIZE - size) / 2);

        ctx.fillStyle = APP_COLORS[row]![col]!;
        ctx.fillRect(x + offset, y + offset, size, size);
      }
    }
  }

  // selector
  if (!animation.isOutro) {
    const finalSelectorX =
      GRID_START_X + selectedCol * (CELL_SIZE + SPACING) - 4;
    const finalSelectorY =
      GRID_START_Y + selectedRow * (CELL_SIZE + SPACING) - 4;

    const dx = finalSelectorX - selectorX;
    const dy = finalSelectorY - selectorY;

    if (dx > 0) {
      selectorX += ANIMATION_SPEED * (deltaTime / 1000);
      if (selectorX > finalSelectorX) selectorX = finalSelectorX;
    } else if (dx < 0) {
      selectorX -= ANIMATION_SPEED * (deltaTime / 1000);
      if (selectorX < finalSelectorX) selectorX = finalSelectorX;
    } else if (dy > 0) {
      selectorY += ANIMATION_SPEED * (deltaTime / 1000);
      if (selectorY > finalSelectorY) selectorY = finalSelectorY;
    } else if (dy < 0) {
      selectorY -= ANIMATION_SPEED * (deltaTime / 1000);
      if (selectorY < finalSelectorY) selectorY = finalSelectorY;
    }
  }

  ctx.fillStyle = APP_COLORS[selectedRow]![selectedCol]!;

  const selectorDrawY = selectorY;
  const offsets = [0, 3, 7, 11, 15, 19, 22];
  for (const offset of offsets) {
    ctx.fillRect(selectorX + offset, selectorDrawY + 0, 2, 1);
    ctx.fillRect(selectorX + offset, selectorDrawY + 23, 2, 1);
    ctx.fillRect(selectorX + 0, selectorDrawY + offset, 1, 2);
    ctx.fillRect(selectorX + 23, selectorDrawY + offset, 1, 2);
  }

  // preview
  ctx.fillRect(192, 96, 32, 32);

  ctx.globalAlpha = animation.textOpacity;
  ctx.font = "7px NDS7";
  ctx.fillStyle = "#000000";
  ctx.textBaseline = "top";
  fillTextHCentered(ctx, "Choose color", 177, 69, 62);
});

const handleActivateB = async () => {
  if (isAnimating.value) return;

  app.setColor(originalSelectedCol, originalSelectedRow);
  await animateOutro();
  store.closeSubMenu();
};

const handleActivateA = () => {
  if (isAnimating.value) return;

  app.save();

  if (app.color.hex !== originalColor) {
    achievements.unlock("settings_color_change");
  }

  if (!achievements.advancement.colors.includes(app.color.hex)) {
    achievements.advancement.colors.push(app.color.hex);
    if (achievements.advancement.colors.length === APP_COLORS.flat().length) {
      achievements.unlock("settings_color_try_all");
    }
  }

  confirmationModal.open({
    text: $t("settings.user.color.confirmation"),
    onClosed: async () => {
      await animateOutro();
      store.closeSubMenu();
    },
    timeout: 2000,
  });
};
</script>

<template>
  <CommonButtons
    :y-offset="0"
    :b-label="$t('common.cancel')"
    :a-label="$t('common.confirm')"
    @activate-b="handleActivateB"
    @activate-a="handleActivateA"
  />
</template>
