<script lang="ts">
  import { createEventDispatcher, onDestroy, onMount, tick } from "svelte";
  import type { FileExplorerEntry, FileExplorerRoot } from "$lib/types/hud";

  export let open = false;
  export let mode: "open" | "save" = "open";
  export let title = "";
  export let currentPath = "";
  export let parentPath: string | null = null;
  export let roots: FileExplorerRoot[] = [];
  export let entries: FileExplorerEntry[] = [];
  export let selectedPath = "";
  export let fileName = "";
  export let pathLabel = "Path";
  export let fileNameLabel = "File name";
  export let cancelLabel = "Cancel";
  export let confirmLabel = "Open";
  export let emptyHint = "No file entries";
  export let csvHint = "*.csv";
  export let busyLabel = "Processing...";
  export let upLabel = "↑ Up";
  export let nameColumnLabel = "Name";
  export let sizeColumnLabel = "Size";
  export let modifiedColumnLabel = "Modified";
  export let isBusy = false;

  const dragViewportPadding = 14;
  let overlayEl: HTMLDivElement | null = null;
  let modalEl: HTMLDivElement | null = null;
  let activePointerId: number | null = null;
  let dragStartX = 0;
  let dragStartY = 0;
  let dragOriginX = 0;
  let dragOriginY = 0;
  let dragModalWidth = 0;
  let dragModalHeight = 0;
  let modalOffsetX = 0;
  let modalOffsetY = 0;
  let isDragging = false;
  let wasOpen = false;

  const dispatch = createEventDispatcher<{
    close: void;
    navigate: string;
    confirm: void;
  }>();

  $: selectedEntry = entries.find((entry) => entry.path === selectedPath) ?? null;
  $: canConfirm =
    mode === "open"
      ? Boolean(selectedEntry && !selectedEntry.isDir && !isBusy)
      : Boolean(fileName.trim().length > 0 && !isBusy);
  $: if (open && !wasOpen) {
    wasOpen = true;
    modalOffsetX = 0;
    modalOffsetY = 0;
    stopDrag();
    void tick().then(() => clampModalOffset());
  }
  $: if (!open && wasOpen) {
    wasOpen = false;
    stopDrag();
  }

  function formatFileSize(value: number | null | undefined): string {
    if (!Number.isFinite(value ?? Number.NaN) || value == null) {
      return "--";
    }

    if (value < 1024) {
      return `${value} B`;
    }
    if (value < 1024 * 1024) {
      return `${(value / 1024).toFixed(1)} KB`;
    }

    return `${(value / (1024 * 1024)).toFixed(1)} MB`;
  }

  function formatModifiedTime(value: number | null | undefined): string {
    if (!Number.isFinite(value ?? Number.NaN) || value == null) {
      return "--";
    }

    try {
      return new Date(Number(value)).toLocaleString();
    } catch {
      return "--";
    }
  }

  function navigate(path: string): void {
    if (!path || isBusy) {
      return;
    }
    dispatch("navigate", path);
  }

  function selectEntry(entry: FileExplorerEntry): void {
    selectedPath = entry.path;
    if (mode === "save" && !entry.isDir) {
      fileName = entry.name;
    }
  }

  function activateEntry(entry: FileExplorerEntry): void {
    if (entry.isDir) {
      navigate(entry.path);
      return;
    }

    selectedPath = entry.path;
    if (mode === "save") {
      fileName = entry.name;
      return;
    }

    if (canConfirm) {
      dispatch("confirm");
    }
  }

  function closeModal(): void {
    if (isBusy) {
      return;
    }
    dispatch("close");
  }

  function confirmSelection(): void {
    if (!canConfirm) {
      return;
    }
    dispatch("confirm");
  }

  function clamp(value: number, min: number, max: number): number {
    return Math.min(max, Math.max(min, value));
  }

  function resolveDragRange(modalSize: number, viewportSize: number): { min: number; max: number } {
    const centeredGap = (viewportSize - modalSize) / 2;
    const min = dragViewportPadding - centeredGap;
    const max = centeredGap - dragViewportPadding;

    if (!Number.isFinite(min) || !Number.isFinite(max) || min > max) {
      return { min: 0, max: 0 };
    }

    return { min, max };
  }

  function clampModalOffset(): void {
    if (!open || !modalEl) {
      return;
    }

    const viewportWidth = overlayEl?.clientWidth ?? window.innerWidth;
    const viewportHeight = overlayEl?.clientHeight ?? window.innerHeight;
    const rect = modalEl.getBoundingClientRect();
    const xRange = resolveDragRange(rect.width, viewportWidth);
    const yRange = resolveDragRange(rect.height, viewportHeight);

    modalOffsetX = clamp(modalOffsetX, xRange.min, xRange.max);
    modalOffsetY = clamp(modalOffsetY, yRange.min, yRange.max);
  }

  function stopDrag(): void {
    activePointerId = null;
    isDragging = false;
    window.removeEventListener("pointermove", handlePointerMove);
    window.removeEventListener("pointerup", handlePointerUp);
    window.removeEventListener("pointercancel", handlePointerUp);
  }

  function handlePointerMove(event: PointerEvent): void {
    if (activePointerId == null || event.pointerId !== activePointerId) {
      return;
    }

    event.preventDefault();
    const viewportWidth = overlayEl?.clientWidth ?? window.innerWidth;
    const viewportHeight = overlayEl?.clientHeight ?? window.innerHeight;
    const xRange = resolveDragRange(dragModalWidth, viewportWidth);
    const yRange = resolveDragRange(dragModalHeight, viewportHeight);
    const rawX = dragOriginX + (event.clientX - dragStartX);
    const rawY = dragOriginY + (event.clientY - dragStartY);

    modalOffsetX = clamp(rawX, xRange.min, xRange.max);
    modalOffsetY = clamp(rawY, yRange.min, yRange.max);
    isDragging = true;
  }

  function handlePointerUp(event: PointerEvent): void {
    if (activePointerId == null || event.pointerId !== activePointerId) {
      return;
    }

    stopDrag();
  }

  function startDrag(event: PointerEvent): void {
    if (event.button !== 0 || !event.isPrimary || !modalEl) {
      return;
    }

    if (event.target instanceof HTMLElement && event.target.closest("button, input, select, textarea, a")) {
      return;
    }

    const rect = modalEl.getBoundingClientRect();
    dragModalWidth = rect.width;
    dragModalHeight = rect.height;
    dragStartX = event.clientX;
    dragStartY = event.clientY;
    dragOriginX = modalOffsetX;
    dragOriginY = modalOffsetY;
    activePointerId = event.pointerId;
    isDragging = true;

    window.addEventListener("pointermove", handlePointerMove, { passive: false });
    window.addEventListener("pointerup", handlePointerUp);
    window.addEventListener("pointercancel", handlePointerUp);
  }

  function handleViewportResize(): void {
    clampModalOffset();
  }

  onMount(() => {
    window.addEventListener("resize", handleViewportResize);
    return () => {
      window.removeEventListener("resize", handleViewportResize);
      stopDrag();
    };
  });

  onDestroy(() => {
    stopDrag();
  });
</script>

{#if open}
  <div
    bind:this={overlayEl}
    class="explorer-overlay"
    role="presentation"
    on:click={(event) => {
      if (event.target === event.currentTarget) {
        closeModal();
      }
    }}
  >
    <div
      bind:this={modalEl}
      class="explorer-modal"
      class:is-dragging={isDragging}
      role="dialog"
      aria-modal="true"
      aria-label={title}
      tabindex="-1"
      style={`--explorer-drag-x: ${modalOffsetX}px; --explorer-drag-y: ${modalOffsetY}px;`}
      on:keydown={(event) => {
        if (event.key === "Escape") {
          event.preventDefault();
          closeModal();
        }
      }}
    >
      <header
        class="explorer-header"
        role="toolbar"
        tabindex="-1"
        aria-label="Dialog drag bar"
        on:pointerdown={startDrag}
      >
        <div class="explorer-title-wrap">
          <span class="title-pulse" aria-hidden="true"></span>
          <h3 class="explorer-title">{title}</h3>
        </div>
        <button type="button" class="header-close-btn" aria-label="Close" on:click={closeModal}>×</button>
      </header>

      <div class="explorer-toolbar">
        <button
          type="button"
          class="tool-btn"
          disabled={!parentPath || isBusy}
          on:click={() => parentPath && navigate(parentPath)}
        >
          {upLabel}
        </button>
        <div class="path-field" title={currentPath}>
          <span class="path-label">{pathLabel}</span>
          <span class="path-value">{currentPath}</span>
        </div>
      </div>

      <div class="explorer-content">
        <aside class="roots-list" aria-label="Roots">
          {#each roots as root (root.path)}
            <button
              type="button"
              class="root-btn"
              class:is-active={currentPath === root.path}
              on:click={() => navigate(root.path)}
            >
              {root.label}
            </button>
          {/each}
        </aside>

        <section class="entries-wrap" aria-label="Entries">
          <div class="entries-head">
            <span>{nameColumnLabel}</span>
            <span>{sizeColumnLabel}</span>
            <span>{modifiedColumnLabel}</span>
          </div>

          {#if entries.length === 0}
            <p class="entries-empty">{emptyHint}</p>
          {:else}
            <div class="entries-body">
              {#each entries as entry (entry.path)}
                <button
                  type="button"
                  class="entry-row"
                  class:is-selected={entry.path === selectedPath}
                  on:click={() => selectEntry(entry)}
                  on:dblclick={() => activateEntry(entry)}
                >
                  <span class="entry-name">
                    <span class="entry-icon" aria-hidden="true">{entry.isDir ? "DIR" : "CSV"}</span>
                    <span class="entry-text">{entry.name}</span>
                  </span>
                  <span class="entry-size">{entry.isDir ? "--" : formatFileSize(entry.sizeBytes)}</span>
                  <span class="entry-time">{formatModifiedTime(entry.modifiedMs)}</span>
                </button>
              {/each}
            </div>
          {/if}
        </section>
      </div>

      <footer class="explorer-footer">
        {#if mode === "save"}
          <label class="name-input-wrap">
            <span class="name-label">{fileNameLabel}</span>
            <input
              class="name-input"
              type="text"
              bind:value={fileName}
              placeholder="joyson_export.csv"
              autocomplete="off"
            />
          </label>
        {:else}
          <p class="csv-hint">{csvHint}</p>
        {/if}

        <div class="footer-actions">
          <button type="button" class="action-btn cancel" disabled={isBusy} on:click={closeModal}>{cancelLabel}</button>
          <button type="button" class="action-btn confirm" disabled={!canConfirm} on:click={confirmSelection}>
            {isBusy ? busyLabel : confirmLabel}
          </button>
        </div>
      </footer>
    </div>
  </div>
{/if}

<style>
  .explorer-overlay {
    position: absolute;
    inset: 0;
    z-index: 20;
    display: grid;
    place-items: center;
    background:
      radial-gradient(circle at 30% 12%, rgb(var(--hud-glow-rgb) / 0.08), transparent 42%),
      radial-gradient(circle at 84% 10%, rgb(var(--hud-glow-alt-rgb) / 0.07), transparent 40%),
      rgb(0 0 0 / 0.6);
    backdrop-filter: blur(3px);
    padding: clamp(0.65rem, 2.4vw, 1.25rem);
  }

  .explorer-modal {
    position: relative;
    display: grid;
    grid-template-rows: auto auto minmax(0, 1fr) auto;
    inline-size: min(1020px, 100%);
    block-size: min(720px, 100%);
    max-inline-size: calc(100% - 2 * clamp(0.65rem, 2.4vw, 1.25rem));
    max-block-size: calc(100% - 2 * clamp(0.65rem, 2.4vw, 1.25rem));
    border: 1px solid rgb(var(--hud-border-rgb) / 0.34);
    border-radius: 0.72rem;
    background:
      linear-gradient(
        172deg,
        rgb(var(--hud-surface-rgb) / 0.96) 0%,
        rgb(var(--hud-surface-deep-rgb) / 0.96) 52%,
        rgb(var(--hud-surface-deep-rgb) / 0.98) 100%
      ),
      radial-gradient(circle at 18% 0, rgb(var(--hud-glow-rgb) / 0.06), transparent 42%),
      radial-gradient(circle at 90% 0, rgb(var(--hud-glow-alt-rgb) / 0.05), transparent 38%);
    box-shadow:
      inset 0 1px 0 rgb(var(--hud-border-strong-rgb) / 0.08),
      0 22px 50px rgb(0 0 0 / 0.52);
    overflow: hidden;
    transform: translate3d(var(--explorer-drag-x, 0), var(--explorer-drag-y, 0), 0);
  }

  .explorer-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.65rem;
    padding: 0.72rem 0.85rem 0.65rem;
    border-bottom: 1px solid rgb(var(--hud-border-rgb) / 0.28);
    background: linear-gradient(180deg, rgb(var(--hud-surface-alt-rgb) / 0.6), transparent);
    cursor: grab;
    user-select: none;
    touch-action: none;
  }

  .explorer-modal.is-dragging .explorer-header {
    cursor: grabbing;
  }

  .explorer-title-wrap {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    min-width: 0;
  }

  .title-pulse {
    inline-size: 0.5rem;
    block-size: 0.5rem;
    border-radius: 999px;
    background: var(--hud-lime);
    box-shadow: 0 0 0 2px rgb(var(--hud-lime-rgb) / 0.14);
  }

  .explorer-title {
    margin: 0;
    color: rgb(var(--hud-text-main-rgb) / 0.98);
    font-size: 0.92rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
  }

  .header-close-btn {
    inline-size: 2rem;
    block-size: 1.64rem;
    border: 1px solid rgb(var(--hud-border-rgb) / 0.36);
    border-radius: 0.36rem;
    background: rgb(var(--hud-surface-rgb) / 0.9);
    color: rgb(var(--hud-text-main-rgb) / 0.9);
    font-size: 1rem;
    line-height: 1;
    cursor: pointer;
    transition:
      border-color 180ms ease,
      color 180ms ease;
  }

  .header-close-btn:hover {
    border-color: rgb(var(--hud-orange-rgb) / 0.6);
    color: rgb(var(--hud-orange-rgb) / 0.96);
  }

  .explorer-toolbar {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    gap: 0.58rem;
    padding: 0.62rem 0.85rem;
    border-bottom: 1px solid rgb(var(--hud-border-rgb) / 0.22);
    background: linear-gradient(90deg, rgb(var(--hud-glow-rgb) / 0.03), transparent 44%, rgb(var(--hud-glow-alt-rgb) / 0.02));
  }

  .tool-btn {
    min-block-size: 1.95rem;
    border: 1px solid rgb(var(--hud-border-rgb) / 0.36);
    border-radius: 0.42rem;
    padding: 0.25rem 0.65rem;
    background: rgb(var(--hud-surface-alt-rgb) / 0.86);
    color: rgb(var(--hud-text-main-rgb) / 0.94);
    font-size: 0.72rem;
    letter-spacing: 0.05em;
    cursor: pointer;
    transition:
      border-color 180ms ease,
      box-shadow 180ms ease,
      opacity 180ms ease;
  }

  .tool-btn:hover:not(:disabled) {
    border-color: rgb(var(--hud-cyan-rgb) / 0.46);
    box-shadow: inset 0 0 0 1px rgb(var(--hud-border-strong-rgb) / 0.08);
  }

  .tool-btn:disabled {
    opacity: 0.58;
    cursor: default;
  }

  .path-field {
    min-width: 0;
    display: grid;
    grid-template-columns: auto 1fr;
    align-items: center;
    gap: 0.45rem;
    border: 1px solid rgb(var(--hud-border-rgb) / 0.32);
    border-radius: 0.42rem;
    padding: 0.25rem 0.55rem;
    background: rgb(var(--hud-surface-rgb) / 0.76);
  }

  .path-label {
    color: rgb(var(--hud-text-dim-rgb) / 0.84);
    font-size: 0.63rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    white-space: nowrap;
  }

  .path-value {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: rgb(var(--hud-text-main-rgb) / 0.97);
    font-size: 0.76rem;
    letter-spacing: 0.03em;
  }

  .explorer-content {
    min-height: 0;
    display: grid;
    grid-template-columns: 200px minmax(0, 1fr);
    gap: 0.62rem;
    padding: 0.72rem 0.85rem;
  }

  .roots-list {
    min-height: 0;
    display: grid;
    grid-auto-rows: min-content;
    gap: 0.35rem;
    border: 1px solid rgb(95 132 158 / 0.28);
    border-radius: 0.5rem;
    padding: 0.42rem;
    background: rgb(7 13 18 / 0.78);
    overflow: auto;
  }

  .root-btn {
    border: 1px solid transparent;
    border-radius: 0.34rem;
    padding: 0.35rem 0.45rem;
    text-align: left;
    background: transparent;
    color: rgb(167 189 208 / 0.94);
    font-size: 0.74rem;
    letter-spacing: 0.04em;
    cursor: pointer;
    transition:
      border-color 180ms ease,
      background-color 180ms ease,
      color 180ms ease;
  }

  .root-btn:hover {
    border-color: rgb(62 232 255 / 0.3);
    color: #e5f5ff;
  }

  .root-btn.is-active {
    border-color: rgb(133 255 68 / 0.46);
    background: rgb(24 33 22 / 0.7);
    color: rgb(237 255 228 / 0.98);
  }

  .entries-wrap {
    min-height: 0;
    display: grid;
    grid-template-rows: auto minmax(0, 1fr);
    border: 1px solid rgb(95 132 158 / 0.28);
    border-radius: 0.5rem;
    overflow: hidden;
    background: rgb(6 11 16 / 0.78);
  }

  .entries-head {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 110px 180px;
    gap: 0.45rem;
    padding: 0.44rem 0.55rem;
    border-bottom: 1px solid rgb(95 132 158 / 0.24);
    color: rgb(141 164 183 / 0.88);
    font-size: 0.62rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
  }

  .entries-empty {
    margin: 0;
    display: grid;
    place-items: center;
    color: rgb(148 171 187 / 0.86);
    font-size: 0.75rem;
    letter-spacing: 0.03em;
  }

  .entries-body {
    min-height: 0;
    overflow: auto;
    padding: 0.18rem;
    display: grid;
    grid-auto-rows: min-content;
    gap: 0.18rem;
  }

  .entry-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 110px 180px;
    gap: 0.45rem;
    align-items: center;
    border: 1px solid transparent;
    border-radius: 0.32rem;
    padding: 0.32rem 0.4rem;
    background: transparent;
    color: rgb(204 227 243 / 0.94);
    cursor: pointer;
    text-align: left;
    transition:
      border-color 160ms ease,
      background-color 160ms ease;
  }

  .entry-row:hover {
    border-color: rgb(62 232 255 / 0.26);
    background: rgb(11 18 24 / 0.56);
  }

  .entry-row.is-selected {
    border-color: rgb(133 255 68 / 0.46);
    background:
      linear-gradient(180deg, rgb(24 33 22 / 0.86), rgb(14 21 14 / 0.78)),
      radial-gradient(circle at 6% 50%, rgb(133 255 68 / 0.15), transparent 58%);
    box-shadow: inset 0 0 0 1px rgb(230 255 220 / 0.06);
  }

  .entry-name {
    min-width: 0;
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
  }

  .entry-icon {
    inline-size: 2.15rem;
    block-size: 1.2rem;
    border: 1px solid rgb(95 132 158 / 0.36);
    border-radius: 0.22rem;
    display: grid;
    place-items: center;
    color: rgb(150 177 198 / 0.9);
    font-size: 0.54rem;
    letter-spacing: 0.08em;
    background: rgb(9 16 22 / 0.72);
    flex-shrink: 0;
  }

  .entry-row.is-selected .entry-icon {
    border-color: rgb(133 255 68 / 0.44);
    color: rgb(214 252 190 / 0.95);
  }

  .entry-text {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 0.76rem;
    letter-spacing: 0.03em;
  }

  .entry-size,
  .entry-time {
    color: rgb(152 176 194 / 0.88);
    font-size: 0.68rem;
    letter-spacing: 0.03em;
    white-space: nowrap;
    text-align: right;
  }

  .explorer-footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.7rem;
    border-top: 1px solid rgb(95 132 158 / 0.24);
    padding: 0.68rem 0.85rem 0.76rem;
    background: linear-gradient(0deg, rgb(5 10 14 / 0.72), transparent);
  }

  .name-input-wrap {
    min-width: 0;
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    align-items: center;
    gap: 0.45rem;
    flex: 1;
  }

  .name-label {
    color: rgb(140 163 181 / 0.86);
    font-size: 0.66rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    white-space: nowrap;
  }

  .name-input {
    min-inline-size: 0;
    border: 1px solid rgb(95 132 158 / 0.36);
    border-radius: 0.36rem;
    padding: 0.4rem 0.55rem;
    background: rgb(8 14 19 / 0.8);
    color: rgb(223 242 255 / 0.97);
    font-size: 0.76rem;
    letter-spacing: 0.03em;
    outline: none;
    transition:
      border-color 170ms ease,
      box-shadow 170ms ease;
  }

  .name-input:focus-visible {
    border-color: rgb(62 232 255 / 0.52);
    box-shadow: 0 0 0 2px rgb(62 232 255 / 0.14);
  }

  .csv-hint {
    margin: 0;
    color: rgb(150 173 189 / 0.9);
    font-size: 0.7rem;
    letter-spacing: 0.04em;
  }

  .footer-actions {
    display: inline-flex;
    align-items: center;
    gap: 0.42rem;
  }

  .action-btn {
    min-block-size: 2rem;
    border-radius: 999px;
    padding: 0.28rem 0.78rem;
    font-size: 0.74rem;
    letter-spacing: 0.05em;
    cursor: pointer;
    transition:
      border-color 180ms ease,
      opacity 160ms ease,
      box-shadow 180ms ease;
  }

  .action-btn.cancel {
    border: 1px solid rgb(95 132 158 / 0.36);
    background: rgb(9 16 21 / 0.86);
    color: rgb(206 228 244 / 0.94);
  }

  .action-btn.cancel:hover:not(:disabled) {
    border-color: rgb(122 198 255 / 0.48);
  }

  .action-btn.confirm {
    border: 1px solid rgb(133 255 68 / 0.48);
    background:
      linear-gradient(180deg, rgb(25 35 23 / 0.96), rgb(13 20 13 / 0.92)),
      radial-gradient(circle at 50% 0, rgb(133 255 68 / 0.14), transparent 58%);
    color: rgb(240 255 233 / 0.98);
  }

  .action-btn.confirm:hover:not(:disabled) {
    border-color: rgb(176 255 132 / 0.62);
    box-shadow: 0 0 10px rgb(133 255 68 / 0.14);
  }

  .action-btn:disabled {
    opacity: 0.55;
    cursor: default;
  }

  @media (max-width: 900px) {
    .explorer-content {
      grid-template-columns: 1fr;
      grid-template-rows: 140px minmax(0, 1fr);
    }

    .roots-list {
      grid-auto-flow: column;
      grid-auto-columns: minmax(120px, 1fr);
      overflow-x: auto;
      overflow-y: hidden;
    }
  }

  @media (max-width: 640px) {
    .explorer-modal {
      block-size: min(760px, 100%);
    }

    .entries-head,
    .entry-row {
      grid-template-columns: minmax(0, 1fr) 90px;
    }

    .entries-head span:last-child,
    .entry-time {
      display: none;
    }

    .explorer-footer {
      flex-direction: column;
      align-items: stretch;
    }

    .footer-actions {
      justify-content: flex-end;
    }
  }
</style>
