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

const APP_COLOR_TO_FONT_COLOR: Record<string, string> = {
  "#61829a": "#fbfbfb", // cyan
  "#ba4900": "#fbe3d3", // maroon
  "#fb0018": "#fbcbd3", // red
  "#fb8afb": "#fbebfb", // pink
  "#fb9200": "#fbf3e3", // orange
  "#f3e300": "#fbfbdb", // yellow
  "#aafb00": "#fbfbfb", // lime
  "#00fb00": "#fbfbfb", // green
  "#00a238": "#fbfbfb", // dark green
  "#49db8a": "#ebfbf3", // duck
  "#30baf3": "#fbfbfb", // light blue
  "#0059f3": "#dbebfb", // blue
  "#000092": "#ebebfb", // dark blue
  "#8a00d3": "#f3dbfb", // purple
  "#d300eb": "#fbfbfb", // magenta
  "#fb0092": "#ebe3f3", // fuschia
};

const value = defineModel<number>({ required: true });

const props = withDefaults(
  defineProps<{
    x: number;
    title: string;
    digits?: 2 | 4;
    min?: number;
    max?: number;
    disabled?: boolean;
    selected?: boolean;
  }>(),
  {
    digits: 2,
    min: 0,
    max: 99,
    disabled: false,
    selected: false,
  },
);

const emit = defineEmits<{
  select: [];
}>();

const app = useAppStore();
const { assets } = useAssets();
const { onRender, onClick } = useScreen();

const Y = 31;
const SQUARE_HEIGHT = 49;
const ARROW_IMAGE_HEIGHT =
  assets.images.settings.bottomScreen.numberInputUp.rect.height;

const upImage = computed(() => {
  if (props.digits === 2) {
    return props.disabled
      ? assets.images.settings.bottomScreen.numberInputUpDisabled
      : assets.images.settings.bottomScreen.numberInputUp;
  } else {
    return props.disabled
      ? assets.images.settings.bottomScreen.numberInputUpXlDisabled
      : assets.images.settings.bottomScreen.numberInputUpXl;
  }
});

const downImage = computed(() => {
  if (props.digits === 2) {
    return props.disabled
      ? assets.images.settings.bottomScreen.numberInputDownDisabled
      : assets.images.settings.bottomScreen.numberInputDown;
  } else {
    return props.disabled
      ? assets.images.settings.bottomScreen.numberInputDownXlDisabled
      : assets.images.settings.bottomScreen.numberInputDownXl;
  }
});

const squareWidth = computed(() => upImage.value.rect.width);

const SLIDE_OFFSET = 96;
const SLIDE_DURATION = 0.25;
const ARROW_SLIDE_DELAY = 0.15;
const ARROW_SLIDE_DURATION = 0.15;

const animation = reactive({
  offsetY: SLIDE_OFFSET,
  opacity: 0,
  upArrowOffsetY: ARROW_IMAGE_HEIGHT,
  downArrowOffsetY: -ARROW_IMAGE_HEIGHT,
});

const animateIntro = async () => {
  await gsap
    .timeline()
    .to(animation, { offsetY: 0, duration: SLIDE_DURATION, ease: "none" }, 0)
    .to(animation, { opacity: 1, duration: SLIDE_DURATION, ease: "none" }, 0)
    .to(
      animation,
      { upArrowOffsetY: 0, duration: ARROW_SLIDE_DURATION, ease: "none" },
      SLIDE_DURATION + ARROW_SLIDE_DELAY,
    )
    .to(
      animation,
      { downArrowOffsetY: 0, duration: ARROW_SLIDE_DURATION, ease: "none" },
      SLIDE_DURATION + ARROW_SLIDE_DELAY,
    );
};

const animateOutro = async () => {
  await gsap
    .timeline()
    .to(
      animation,
      { offsetY: SLIDE_OFFSET, duration: SLIDE_DURATION, ease: "none" },
      0,
    )
    .to(animation, { opacity: 0, duration: SLIDE_DURATION, ease: "none" }, 0);
};

defineExpose({ animateIntro, animateOutro });

const increase = () => {
  const newValue = value.value + 1;
  value.value = newValue > props.max ? props.min : newValue;
};

const decrease = () => {
  const newValue = value.value - 1;
  value.value = newValue < props.min ? props.max : newValue;
};

onRender((ctx) => {
  ctx.globalAlpha = animation.opacity;
  ctx.translate(0, animation.offsetY);

  // arrow up (clipped to area above the number square)
  ctx.save();
  ctx.beginPath();
  ctx.rect(props.x, 0, squareWidth.value, Y + ARROW_IMAGE_HEIGHT);
  ctx.clip();
  upImage.value.draw(ctx, props.x, Y + animation.upArrowOffsetY);
  ctx.restore();

  // outline
  ctx.fillStyle = "#515151";
  ctx.fillRect(
    props.x,
    Y + ARROW_IMAGE_HEIGHT,
    squareWidth.value,
    SQUARE_HEIGHT,
  );

  // background
  ctx.fillStyle = props.selected ? app.color.hex : "#797979";
  ctx.fillRect(
    props.x + 1,
    Y + ARROW_IMAGE_HEIGHT + 1,
    squareWidth.value - 2,
    SQUARE_HEIGHT - 2,
  );

  // value
  ctx.font = "39px NDS39";
  ctx.letterSpacing = "2px";
  ctx.textBaseline = "top";
  ctx.fillStyle = props.selected
    ? APP_COLOR_TO_FONT_COLOR[app.color.hex]!
    : "#fbfbfb";
  ctx.fillText(
    value.value.toString().padStart(props.digits, "0"),
    props.x + (props.digits === 2 ? 3 : 4),
    Y + ARROW_IMAGE_HEIGHT + Math.floor((SQUARE_HEIGHT - 39) / 2),
  );

  // arrow down (clipped to area below the number square)
  ctx.save();
  ctx.beginPath();
  ctx.rect(
    props.x,
    Y + ARROW_IMAGE_HEIGHT + SQUARE_HEIGHT,
    squareWidth.value,
    ARROW_IMAGE_HEIGHT,
  );
  ctx.clip();
  downImage.value.draw(
    ctx,
    props.x,
    Y + ARROW_IMAGE_HEIGHT + SQUARE_HEIGHT + animation.downArrowOffsetY,
  );
  ctx.restore();

  // title
  ctx.font = "10px NDS10";
  ctx.fillStyle = "#000000";
  ctx.letterSpacing = "0px";
  fillTextCentered(
    ctx,
    props.title,
    props.x + 1,
    // TODO: -10 is needed because fillTextCentered isn't using top baseline
    //       i will change that in the future (maybe)
    Y + ARROW_IMAGE_HEIGHT * 2 + SQUARE_HEIGHT - 6,
    downImage.value.rect.width,
  );
}, 10);

useKeyDown((key) => {
  if (!props.selected || props.disabled) return;
  switch (key) {
    case "NDS_UP":
      increase();
      break;
    case "NDS_DOWN":
      decrease();
      break;
  }
});

onClick((x, y) => {
  if (props.disabled) return;
  if (
    rectContains(
      [props.x, Y, upImage.value.rect.width, ARROW_IMAGE_HEIGHT],
      [x, y],
    )
  ) {
    increase();
    emit("select");
  } else if (
    rectContains(
      [props.x, Y + ARROW_IMAGE_HEIGHT, SQUARE_HEIGHT + 2, SQUARE_HEIGHT + 2],
      [x, y],
    )
  ) {
    emit("select");
  } else if (
    rectContains(
      [
        props.x,
        Y + ARROW_IMAGE_HEIGHT + SQUARE_HEIGHT + 2,
        downImage.value.rect.width,
        ARROW_IMAGE_HEIGHT,
      ],
      [x, y],
    )
  ) {
    decrease();
    emit("select");
  }
});

defineOptions({ render: () => null });
</script>
