<script lang="ts">
  import { DEFAULT_CONFIG, loadConfig } from "../config.ts";
  import createDebug from "debug";
  import "../css/themes/catppuccin-mocha.scss";
  import "../css/style.scss";
  import HeartIcon from "../icons/HeartIcon.svelte";
  import Popup from "../components/ui/Popup.svelte";
  import { unpack_save } from "../save.ts";
  import { PartType } from "../protocol.ts";
  import { onMount } from "svelte";
  import { part_texture_url } from "../textures.ts";
  import Button from "../components/ui/Button.svelte";
  import WarningIcon from "../icons/WarningIcon.svelte";

  let config = DEFAULT_CONFIG;
  // Top-level await. Sets the default config, and overwrites it when the new config is avail. Thanks reactivity!
  (async () => {
    config = await loadConfig();
  })();

  let selected: PartType | null = null;
  let rotation: number | null = null;

  const logger = createDebug("main");
  logger(
    `Hello, world! StarKingdoms ${APP_VERSION} (${COMMIT_HASH}) at your service!`,
  );
  logger("Current view: ShipEditor.svelte");

  let canvas: HTMLCanvasElement;

  let x = window.innerWidth / 2 - 24;
  let y = window.innerHeight / 2 - 24;

  let grid_x = 0;
  let grid_y = 0;

  let scale = 1.0;

  $: grid_size = 48 * scale;

  function handleWheel(e: WheelEvent) {
    let delta = e.shiftKey ? 0.01 : 0.1;
    if (e.deltaY < 0) {
      scale += delta;
    } else {
      scale -= delta;
    }
    if (scale > 5) {
      scale = 5;
    } else if (scale < 0.1) {
      scale = 0.1;
    }
  }

  let isdragging = false;

  let last_cx = -1;
  let last_cy = -1;

  function mouseup(e) {
    isdragging = false;
  }

  function mousemove(e: MouseEvent) {
    if (isdragging) {
      if (last_cx != -1) {
        x += e.clientX - last_cx;
      }
      last_cx = e.clientX;
      if (last_cy != -1) {
        y += e.clientY - last_cy;
      }
      last_cy = e.clientY;
    } else {
      grid_x = Math.round((e.clientX - x) / grid_size - 0.5);
      grid_y = Math.round((e.clientY - y) / grid_size - 0.5);
    }
  }

  function goto(nx: number, ny: number) {
    x = nx + (window.innerWidth / 2 - 24);
    y = ny + (window.innerHeight / 2 - 24);
  }

  if (window.localStorage.getItem("save") === null) {
    window.location.href = "/";
  }

  let save = unpack_save(window.localStorage.getItem("save")!);
  console.log(save);

  let grid: Map<number, Map<number, [PartType, number]>> = new Map();

  function placePart(x: number, y: number, part: PartType, rotation: number) {
    if (!grid.has(x)) {
      grid.set(x, new Map());
    }

    grid.get(x)?.set(y, [part, rotation]);
  }

  let textures: Map<PartType, HTMLImageElement> = new Map();
  let context: CanvasRenderingContext2D | null = null;

  let cantplace_reason: string | null = null;

  function canPlace(): boolean {
    if (selected === null) {
      cantplace_reason = "No part selected";
      return false;
    }
    if (grid.get(grid_x)?.has(grid_y)) {
      cantplace_reason = "Part already exists in this slot";
      return false;
    }
    let counts = part_counts.get(selected)!;
    if (counts.used === counts.available) {
      cantplace_reason = "Out of this part";
      return false;
    }
    cantplace_reason = null;
    return true;
  }

  $: selected, grid, canPlace();

  function render(
    ctx: CanvasRenderingContext2D | null,
    x: number,
    y: number,
    scale: number,
    grid: Map<number, Map<number, PartType>>,
    grid_x: number,
    grid_y: number,
    rotation: number | null,
  ) {
    if (ctx === null) {
      return;
    }

    // clear out the canvas
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    (async () => {
      if (selected !== null) {
        if (!textures.has(selected!)) {
          let promise = new Promise((resolve, reject) => {
            let img = new Image(grid_size, grid_size);
            img.onload = () => resolve(img);
            img.onerror = reject;
            img.src = part_texture_url(selected!, true);
          });

          let img = await promise;

          textures.set(part_type, <HTMLImageElement>img);
        }
        let canvas_x = grid_x * grid_size + x;
        let canvas_y = grid_y * grid_size + y;

        let img = textures.get(selected!)!;
        ctx.save();
        ctx.translate(canvas_x + grid_size / 2, canvas_y + grid_size / 2);
        ctx.rotate((rotation * Math.PI) / 2);
        ctx.globalAlpha = 0.2;
        ctx.drawImage(
          img,
          -grid_size / 2,
          -grid_size / 2,
          grid_size,
          grid_size,
        );
        ctx.globalCompositeOperation = "source-atop";
        if (selected === null) {
          ctx.fillStyle = "rgb(24, 24, 37)";
        } else if (canPlace()) {
          ctx.fillStyle = "rgb(166, 227, 161)";
        } else {
          ctx.fillStyle = "rgb(243, 139, 169)";
        }
        ctx.globalAlpha = 0.5;
        ctx.fillRect(-grid_size / 2, -grid_size / 2, grid_size, grid_size);
        ctx.restore();
      }
    })();

    grid.forEach((row, x_coord) => {
      row.forEach(async ([part_type, rotation], y_coord) => {
        // draw the image
        if (!textures.has(part_type)) {
          let promise = new Promise((resolve, reject) => {
            let img = new Image(grid_size, grid_size);
            img.onload = () => resolve(img);
            img.onerror = reject;
            img.src = part_texture_url(part_type, true);
          });

          let img = await promise;

          textures.set(part_type, <HTMLImageElement>img);
        }

        let img = textures.get(part_type)!;

        let canvas_x = x_coord * grid_size + x;
        let canvas_y = y_coord * grid_size + y;

        ctx.save();
        ctx.translate(canvas_x + grid_size / 2, canvas_y + grid_size / 2);
        ctx.rotate((rotation * Math.PI) / 2);
        ctx.drawImage(
          img,
          -grid_size / 2,
          -grid_size / 2,
          grid_size,
          grid_size,
        );
        ctx.restore();
      });
    });
  }

  $: {
    render(context, x, y, scale, grid, grid_x, grid_y, rotation);
  }

  onMount(() => {
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    context = canvas.getContext("2d");
    render(context, x, y, scale, grid, grid_x, grid_y);
  });

  window.onresize = () => {
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
  };

  let confirm_reload_save = false;
  let confirm_save = false;
  let confirm_quit = false;

  let part_counts: Map<PartType, { used: number; available: number }> =
    new Map();

  function place(x: number, y: number, data: any, rotation: number) {
    if (data === null) {
      return;
    }

    let [type, children] = data;

    placePart(x, y, type, rotation);

    let existing_part_count =
      part_counts.get(<PartType>type) === undefined
        ? { used: 0, available: 0 }
        : part_counts.get(<PartType>type)!;

    part_counts.set(<PartType>type, {
      used: existing_part_count.used + 1,
      available: existing_part_count.available + 1,
    });

    if (children[0] !== null) {
      place(x, y + 1, children[0], (rotation + 2) % 4);
    }
    if (children[1] !== null) {
      place(x + 1, y, children[1], (rotation + 1) % 4);
    }
    if (children[2] !== null) {
      place(x, y - 1, children[2], rotation);
    }
    if (children[3] !== null) {
      place(x - 1, y, children[3], (rotation + 3) % 4);
    }
  }

  function load_save_data(save: any) {
    grid = new Map();
    place(0, 0, [PartType.Hearty, save[0]], 0);
  }

  $: {
    load_save_data(save);
  }

  function reload_btn() {
    if (confirm_reload_save) {
      save = unpack_save(window.localStorage.getItem("save")!);
    }
    confirm_reload_save = !confirm_reload_save;
    setTimeout(() => {
      confirm_reload_save = false;
    }, 5000);
  }

  function save_recursive(x: number, y: number) {
    let [part_type, rotation] = grid.get(x)!.get(y)!;
    let children = [null, null, null, null];
    if (part_type == PartType.Hearty) {
      if (grid.get(x + 1)?.get(y)?.[1] == 1) {
        // right
        children[1] = save_recursive(x + 1, y);
      }
      if (grid.get(x - 1)?.get(y)?.[1] == 3) {
        // left
        children[3] = save_recursive(x - 1, y);
      }
      if (grid.get(x)?.get(y - 1)?.[1] == 0) {
        // up
        children[2] = save_recursive(x, y - 1);
      }
      if (grid.get(x)?.get(y + 1)?.[1] == 2) {
        // down
        children[0] = save_recursive(x, y + 1);
      }
    } else if (part_type == PartType.Cargo) {
    }

    return { part_type: part_type, children: children };
  }

  function save_btn() {
    if (confirm_save) {
      // todo: @ghostly you need to turn this back into a savefile and then call pack_partial on it
      let children = save_recursive(0, 0).children;
      let unused_modules = [];
      for (let [part_type, value] of part_counts) {
        let unused = value.available - value.used;
        if (unused != 0 && part_type != PartType.Hearty) {
          unused_modules.push([part_type, unused]);
        }
      }
      let save_data = { children: children, unused_modules: unused_modules };
      console.log(save_data);
      //window.location.href = "/";
    }
    confirm_save = !confirm_save;
    setTimeout(() => {
      confirm_save = false;
    }, 5000);
  }

  function quit_btn() {
    if (confirm_quit) {
      window.location.href = "/";
    }
    confirm_quit = !confirm_quit;
    setTimeout(() => {
      confirm_quit = false;
    }, 5000);
  }

  function toggle_select(type: PartType) {
    console.log(type);
    if (selected == type) {
      selected = null;
      rotation = null;
    } else {
      selected = type;
      rotation = 0;
    }
    canvas.focus();
  }

  function mousedown(e: MouseEvent) {
    if (e.button === 0) {
      if (canPlace()) {
        if (!grid.has(grid_x)) {
          grid.set(grid_x, new Map());
        }
        grid.get(grid_x)!.set(grid_y, [selected!, rotation]);
        part_counts.set(selected!, {
          used: part_counts.get(selected!)!.used + 1,
          available: part_counts.get(selected!)!.available,
        });
        part_counts = part_counts;
        grid = grid;
      }

      // This gets unset immediately if a normal click
      isdragging = true;
      last_cx = e.clientX;
      last_cy = e.clientY;
    } else if (e.button == 2) {
      // removal
      if (grid.get(grid_x)?.has(grid_y)) {
        let type = grid.get(grid_x)!.get(grid_y)[0]!;
        if (type === PartType.Hearty) {
          return;
        }
        grid.get(grid_x)?.delete(grid_y);
        part_counts.set(type, {
          used: part_counts.get(type)!.used - 1,
          available: part_counts.get(type)!.available,
        });
        part_counts = part_counts;
        grid = grid;
      }
    }
    canvas.focus();
  }

  function keydown(e: KeyboardEvent) {
    if (e.code == "KeyQ") {
      rotation = (rotation - 1) % 4;
    }
    if (e.code == "KeyE") {
      rotation = (rotation + 1) % 4;
    }
  }
</script>

<Popup title="Parts" id="parts" draggable minimizable style="width: 15em;">
  {#each part_counts.entries() as [type, counts]}
    {#if type !== PartType.Hearty}
      <Button
        selected={type === selected}
        style="margin-top: 5px; width: 100%;"
        on:click={() => {
          toggle_select(type);
        }}>
        <img
          style="vertical-align: middle;"
          src={part_texture_url(type, true)}
          width="24"
          height="24"
          alt={type} />
        <span>{type} - {counts.used} used of {counts.available}</span>
      </Button>
    {/if}
  {/each}
  {#if cantplace_reason !== null}
    <span class="server-danger">
      <WarningIcon class="server-danger-icon" />
      Cannot place: {cantplace_reason}
    </span>
  {/if}
</Popup>

<Popup
  title="Tools"
  id="tools"
  draggable
  minimizable
  style="width: min-content;">
  <Button
    style="width: 16em; margin-top: 5px;"
    on:click={() => {
      goto(0, 0);
      scale = 1.0;
    }}>
    Reset Viewport
  </Button>
  <Button
    on:click={reload_btn}
    variant="danger"
    style="min-width: 16em; margin-top: 5px;">
    {#if confirm_reload_save}
      Are you sure? (again to confirm, resets in 5s)
    {:else}
      Reload Save
    {/if}
  </Button>
  <Button
    on:click={save_btn}
    variant="danger"
    style="min-width: 16em; margin-top: 5px;">
    {#if confirm_save}
      Are you sure? Overwrites old save! (again to confirm, resets in 5s)
    {:else}
      Save Changes
    {/if}
  </Button>
  <Button
    on:click={quit_btn}
    variant="danger"
    style="min-width: 16em; margin-top: 5px;">
    {#if confirm_quit}
      Are you sure you want to quit without saving? (again to confirm, resets in
      5s)
    {:else}
      Quit without Save
    {/if}
  </Button>
</Popup>

<canvas
  on:mousedown|preventDefault={mousedown}
  on:mouseup|preventDefault={mouseup}
  on:mousemove|preventDefault={mousemove}
  on:wheel|preventDefault={handleWheel}
  on:keydown={keydown}
  on:contextmenu|preventDefault={() => {
    return false;
  }}
  style="background-size: {grid_size}px {grid_size}px; background-position: {x}px {y}px;"
  tabindex="1"
  bind:this={canvas} />

<span style="position: absolute; top: 10px; right: 10px;">
  ({grid_x}, {grid_y}) {scale.toFixed(2)}x
</span>

<span class="footer-left">
  StarKingdoms Client {APP_VERSION} ({COMMIT_HASH})
</span>
<span class="footer-right">
  Made with <HeartIcon class="footer-icon" /> by the StarKingdoms team
</span>

<style>
  :global(#tools) {
    position: absolute;
    bottom: 0.5em;
    left: 0.5em;
  }

  :global(#parts) {
    position: absolute;
    top: 0.5em;
    left: 0.5em;
  }

  canvas {
    width: 100vw;
    height: 100vh;
    position: absolute;
    top: 0;
    left: 0;
    padding: 0;
    margin: 0;

    background-image: linear-gradient(to right, #adadad20 1px, transparent 1px),
      linear-gradient(to bottom, #adadad20 1px, transparent 1px);
  }
</style>
