import type { Meta, StoryObj } from '@storybook/vue3-vite';
import type { Ref } from 'vue';
import { onBeforeUnmount, ref } from 'vue';
import { UiButton } from '@/components/UiButton';
import {
  UiDialog,
  UiDialogClose,
  UiDialogContent,
  UiDialogTitle,
  UiDialogTrigger,
} from '@/components/UiDialog';
import { UiFileItem } from '@/components/UiFileItem';
import { UiIcon } from '@/components/UiIcon';
import { formatFileSize } from '@/components/UiFileUploadBox/file-validation';
import UiFileUpload from './UiFileUpload.vue';

const meta: Meta<typeof UiFileUpload> = {
  title: 'Patterns/UiFileUpload',
  component: UiFileUpload,
  tags: ['autodocs'],
  decorators: [
    () => ({
      template: '<div class="w-full max-w-lg p-4"><story /></div>',
    }),
  ],
  args: {
    accept: '.pdf,.docx,.txt,application/pdf',
    multiple: true,
    maxFileSizeBytes: 500 * 1024 * 1024,
    disabled: false,
    supportedTypesLabel: 'Supported file types: PDF, DOCX, TXT',
    maxSizeLabel: '',
    dropLabel: 'Drop files here',
    chooseLabel: 'Choose files',
    ariaLabel: 'File upload',
  },
  argTypes: {
    accept: { control: 'text' },
    multiple: { control: 'boolean' },
    maxFileSizeBytes: {
      control: 'number',
      description:
        'Max file size in bytes (validation). When **maxSizeLabel** is blank, the footer shows `Max size: …` from this value.',
    },
    disabled: { control: 'boolean' },
    supportedTypesLabel: {
      control: 'text',
      description:
        'Footer left: supported-types hint. Clear to hide. Edit in Controls.',
    },
    maxSizeLabel: {
      control: 'text',
      description:
        'Footer right: max-size line. Leave blank for auto `Max size: …` from **maxFileSizeBytes**; type any text to override (e.g. custom copy or units).',
    },
    dropLabel: { control: 'text' },
    chooseLabel: { control: 'text' },
    ariaLabel: { control: 'text' },
  },
};

export default meta;
type Story = StoryObj<typeof UiFileUpload>;

type DialogFileRow = {
  id: string;
  name: string;
  typeLabel: string;
  sizeLabel: string;
  progress: number | null;
  error: string | null;
};

type IdCounter = { current: number };

function pushValidationErrorRow(
  rows: Ref<DialogFileRow[]>,
  id: IdCounter,
  e: { message: string; file?: File },
) {
  id.current += 1;
  rows.value.push({
    id: `err-${id.current}`,
    name: e.file?.name ?? 'File',
    typeLabel: '',
    sizeLabel: e.file ? formatFileSize(e.file.size) : '',
    progress: null,
    error: e.message,
  });
}

const MOCK_INTERVAL_MS = 280;
const MOCK_CLEAR_DELAY_MS = 450;
/** Deterministic step sizes for mock upload (Storybook only; avoids `Math.random`). */
const MOCK_PROGRESS_STEPS = [8, 12, 9, 11, 10, 14, 7, 13] as const;

type MockUploadCtx = {
  rows: Ref<DialogFileRow[]>;
  intervals: Set<ReturnType<typeof setInterval>>;
  timeouts: Set<ReturnType<typeof setTimeout>>;
  stepIndices: Map<string, number>;
};

function nextMockProgressValue(current: number, stepIndex: number): number {
  const inc = MOCK_PROGRESS_STEPS[stepIndex % MOCK_PROGRESS_STEPS.length];
  return Math.min(100, current + inc);
}

function stopMockInterval(
  handle: ReturnType<typeof setInterval>,
  intervals: Set<ReturnType<typeof setInterval>>,
) {
  clearInterval(handle);
  intervals.delete(handle);
}

function finalizeMockUploadRow(
  rowId: string,
  timeoutHandle: ReturnType<typeof setTimeout>,
  rows: Ref<DialogFileRow[]>,
  timeouts: Set<ReturnType<typeof setTimeout>>,
) {
  const row = rows.value.find((r) => r.id === rowId);
  if (row) row.progress = null;
  timeouts.delete(timeoutHandle);
}

function scheduleMockUploadComplete(rowId: string, ctx: MockUploadCtx) {
  const timeoutHandle = setTimeout(() => {
    finalizeMockUploadRow(rowId, timeoutHandle, ctx.rows, ctx.timeouts);
  }, MOCK_CLEAR_DELAY_MS);
  ctx.timeouts.add(timeoutHandle);
}

function runMockUploadTick(
  rowId: string,
  intervalHandle: ReturnType<typeof setInterval>,
  ctx: MockUploadCtx,
) {
  const currentRow = ctx.rows.value.find((r) => r.id === rowId);
  if (!currentRow || currentRow.progress == null) {
    stopMockInterval(intervalHandle, ctx.intervals);
    return;
  }
  const stepIndex = ctx.stepIndices.get(rowId) ?? 0;
  const next = nextMockProgressValue(currentRow.progress, stepIndex);
  ctx.stepIndices.set(rowId, stepIndex + 1);
  currentRow.progress = next;
  if (next >= 100) {
    stopMockInterval(intervalHandle, ctx.intervals);
    ctx.stepIndices.delete(rowId);
    scheduleMockUploadComplete(rowId, ctx);
  }
}

function startMockUploadInterval(rowId: string, ctx: MockUploadCtx) {
  ctx.stepIndices.set(rowId, 0);
  const intervalHandle = setInterval(() => {
    runMockUploadTick(rowId, intervalHandle, ctx);
  }, MOCK_INTERVAL_MS);
  ctx.intervals.add(intervalHandle);
}

function docsSource(code: string) {
  return {
    docs: {
      source: {
        code,
      },
    },
  };
}

/** Full SFC for dialog + file list pattern (shared body; `UiFileUpload` props vary per story). */
const DIALOG_PATTERN_TEMPLATE = `<script setup lang="ts">
import { ref } from 'vue'
import {
  UiButton,
  UiDialog,
  UiDialogClose,
  UiDialogContent,
  UiDialogTitle,
  UiDialogTrigger,
  UiFileItem,
  UiFileUpload,
  UiIcon,
} from '@aleph-alpha/ui-library'

const dialogOpen = ref(false)

type Row = {
  id: string
  name: string
  typeLabel: string
  sizeLabel: string
  progress: number | null
  error: string | null
}

const rows = ref<Row[]>([])
let id = 0

function addFiles(files: File[]) {
  for (const f of files) {
    id += 1
    const ext = f.name.split('.').pop()?.toUpperCase() ?? 'FILE'
    rows.value.push({
      id: \`f-\${id}\`,
      name: f.name,
      typeLabel: ext,
      sizeLabel: \`\${f.size} B\`,
      progress: null,
      error: null,
    })
  }
}

function onError(e: { message: string; file?: File }) {
  id += 1
  rows.value.push({
    id: \`err-\${id}\`,
    name: e.file?.name ?? 'File',
    typeLabel: '',
    sizeLabel: e.file?.size != null ? \`\${e.file.size} B\` : '',
    progress: null,
    error: e.message,
  })
}

function remove(r: Row) {
  rows.value = rows.value.filter((x) => x.id !== r.id)
}
</script>

<template>
  <UiDialog v-model:open="dialogOpen">
    <UiDialogTrigger as-child>
      <UiButton variant="outline">Open file upload</UiButton>
    </UiDialogTrigger>
    <UiDialogContent max-width="max-w-lg" class="gap-4">
      <template #header>
        <div class="flex items-start justify-between gap-4">
          <UiDialogTitle class="heading-s">File upload</UiDialogTitle>
          <UiDialogClose as-child>
            <UiButton variant="ghost" class="size-9 shrink-0 p-0" aria-label="Close dialog">
              <UiIcon name="x" :size="20" />
            </UiButton>
          </UiDialogClose>
        </div>
      </template>

      <div class="flex flex-col gap-4">
        <UiFileUpload
__FILE_UPLOAD_PROPS__
          @update:files="addFiles"
          @error="onError"
        />
        <ul v-if="rows.length" class="flex flex-col gap-2" aria-label="Selected files">
          <li v-for="r in rows" :key="r.id">
            <UiFileItem
              :filename="r.name"
              :type-label="r.typeLabel || undefined"
              :size-label="r.sizeLabel || undefined"
              :progress="r.progress"
              :error="r.error"
              @remove="remove(r)"
            />
          </li>
        </ul>
      </div>

      <template #actions>
        <UiDialogClose as-child>
          <UiButton type="button" variant="ghost">Cancel</UiButton>
        </UiDialogClose>
        <UiDialogClose as-child>
          <UiButton type="button">Save changes</UiButton>
        </UiDialogClose>
      </template>
    </UiDialogContent>
  </UiDialog>
</template>`;

function dialogDocsCode(fileUploadPropsLines: string): string {
  const trimmed = fileUploadPropsLines.trim();
  const indented = trimmed
    ? `${trimmed
        .split('\n')
        .map((line) => `          ${line.trim()}`)
        .join('\n')}\n`
    : '';
  return DIALOG_PATTERN_TEMPLATE.replace('__FILE_UPLOAD_PROPS__', indented);
}

/** Docs snippet for **Default**: dialog + trigger + `UiFileUpload` + file list (matches `meta.args`). */
const defaultDialogTemplateSource = `<script setup lang="ts">
import { ref } from 'vue'
import {
  UiButton,
  UiDialog,
  UiDialogClose,
  UiDialogContent,
  UiDialogTitle,
  UiDialogTrigger,
  UiFileItem,
  UiFileUpload,
  UiIcon,
} from '@aleph-alpha/ui-library'

const dialogOpen = ref(false)

type Row = {
  id: string
  name: string
  typeLabel: string
  sizeLabel: string
  progress: number | null
  error: string | null
}

const rows = ref<Row[]>([])
let id = 0

function addFiles(files: File[]) {
  for (const f of files) {
    id += 1
    const ext = f.name.split('.').pop()?.toUpperCase() ?? 'FILE'
    rows.value.push({
      id: \`f-\${id}\`,
      name: f.name,
      typeLabel: ext,
      sizeLabel: \`\${f.size} B\`,
      progress: null,
      error: null,
    })
  }
}

function onError(e: { message: string; file?: File }) {
  id += 1
  rows.value.push({
    id: \`err-\${id}\`,
    name: e.file?.name ?? 'File',
    typeLabel: '',
    sizeLabel: e.file?.size != null ? \`\${e.file.size} B\` : '',
    progress: null,
    error: e.message,
  })
}

function remove(r: Row) {
  rows.value = rows.value.filter((x) => x.id !== r.id)
}
</script>

<template>
  <UiDialog v-model:open="dialogOpen">
    <UiDialogTrigger as-child>
      <UiButton variant="outline">Open file upload</UiButton>
    </UiDialogTrigger>
    <UiDialogContent max-width="max-w-lg" class="gap-4">
      <template #header>
        <div class="flex items-start justify-between gap-4">
          <UiDialogTitle class="heading-s">File upload</UiDialogTitle>
          <UiDialogClose as-child>
            <UiButton variant="ghost" class="size-9 shrink-0 p-0" aria-label="Close dialog">
              <UiIcon name="x" :size="20" />
            </UiButton>
          </UiDialogClose>
        </div>
      </template>

      <div class="flex flex-col gap-4">
        <UiFileUpload
          accept=".pdf,.docx,.txt,application/pdf"
          :multiple="true"
          :max-file-size-bytes="524288000"
          supported-types-label="Supported file types: PDF, DOCX, TXT"
          @update:files="addFiles"
          @error="onError"
        />
        <ul v-if="rows.length" class="flex flex-col gap-2" aria-label="Selected files">
          <li v-for="r in rows" :key="r.id">
            <UiFileItem
              :filename="r.name"
              :type-label="r.typeLabel || undefined"
              :size-label="r.sizeLabel || undefined"
              :progress="r.progress"
              :error="r.error"
              @remove="remove(r)"
            />
          </li>
        </ul>
      </div>

      <template #actions>
        <UiDialogClose as-child>
          <UiButton type="button" variant="ghost">Cancel</UiButton>
        </UiDialogClose>
        <UiDialogClose as-child>
          <UiButton type="button">Save changes</UiButton>
        </UiDialogClose>
      </template>
    </UiDialogContent>
  </UiDialog>
</template>`;

function renderInDialog(args: Story['args']) {
  return {
    components: {
      UiDialog,
      UiDialogTrigger,
      UiDialogContent,
      UiDialogTitle,
      UiDialogClose,
      UiButton,
      UiIcon,
      UiFileUpload,
      UiFileItem,
    },
    setup() {
      const dialogOpen = ref(false);
      const rows = ref<DialogFileRow[]>([]);
      const id = { current: 0 };

      function addFiles(files: File[]) {
        for (const f of files) {
          id.current += 1;
          const ext = f.name.split('.').pop()?.toUpperCase() ?? 'FILE';
          rows.value.push({
            id: `f-${id.current}`,
            name: f.name,
            typeLabel: ext,
            sizeLabel: formatFileSize(f.size),
            progress: null,
            error: null,
          });
        }
      }

      function onError(e: { message: string; file?: File }) {
        pushValidationErrorRow(rows, id, e);
      }

      function remove(r: DialogFileRow) {
        rows.value = rows.value.filter((x) => x.id !== r.id);
      }

      return { rows, addFiles, onError, remove, dialogOpen, args };
    },
    template: `
      <UiDialog v-model:open="dialogOpen">
        <UiDialogTrigger as-child>
          <UiButton variant="outline">Open file upload</UiButton>
        </UiDialogTrigger>
        <UiDialogContent max-width="max-w-lg" class="gap-4">
          <template #header>
            <div class="flex items-start justify-between gap-4">
              <UiDialogTitle class="heading-s">File upload</UiDialogTitle>
              <UiDialogClose as-child>
                <UiButton variant="ghost" class="size-9 shrink-0 p-0" aria-label="Close dialog">
                  <UiIcon name="x" :size="20" />
                </UiButton>
              </UiDialogClose>
            </div>
          </template>

          <div class="flex flex-col gap-4">
            <UiFileUpload v-bind="args" @update:files="addFiles" @error="onError" />
            <ul v-if="rows.length" class="flex flex-col gap-2" aria-label="Selected files">
              <li v-for="r in rows" :key="r.id">
                <UiFileItem
                  :filename="r.name"
                  :type-label="r.typeLabel || undefined"
                  :size-label="r.sizeLabel || undefined"
                  :progress="r.progress"
                  :error="r.error"
                  @remove="remove(r)"
                />
              </li>
            </ul>
          </div>

          <template #actions>
            <UiDialogClose as-child>
              <UiButton type="button" variant="ghost">Cancel</UiButton>
            </UiDialogClose>
            <UiDialogClose as-child>
              <UiButton type="button">Save changes</UiButton>
            </UiDialogClose>
          </template>
        </UiDialogContent>
      </UiDialog>
    `,
  };
}

/**
 * Same dialog shell as `renderInDialog`, but each accepted file mock-uploads (progress 0→100) then the progress bar clears (`progress` → `null`) so the row shows as finished without the loading UI.
 * @param args - Story args passed through to `UiFileUpload`.
 */
function renderInDialogWithMockUpload(args: Story['args']) {
  return {
    components: {
      UiDialog,
      UiDialogTrigger,
      UiDialogContent,
      UiDialogTitle,
      UiDialogClose,
      UiButton,
      UiIcon,
      UiFileUpload,
      UiFileItem,
    },
    setup() {
      const dialogOpen = ref(false);
      const rows = ref<DialogFileRow[]>([]);
      const id = { current: 0 };
      const intervals = new Set<ReturnType<typeof setInterval>>();
      const timeouts = new Set<ReturnType<typeof setTimeout>>();
      const stepIndices = new Map<string, number>();
      const mockCtx: MockUploadCtx = { rows, intervals, timeouts, stepIndices };

      onBeforeUnmount(() => {
        intervals.forEach((h) => clearInterval(h));
        timeouts.forEach((h) => clearTimeout(h));
        intervals.clear();
        timeouts.clear();
        stepIndices.clear();
      });

      function addFiles(files: File[]) {
        for (const f of files) {
          id.current += 1;
          const ext = f.name.split('.').pop()?.toUpperCase() ?? 'FILE';
          const rowId = `f-${id.current}`;
          rows.value.push({
            id: rowId,
            name: f.name,
            typeLabel: ext,
            sizeLabel: formatFileSize(f.size),
            progress: 0,
            error: null,
          });
          startMockUploadInterval(rowId, mockCtx);
        }
      }

      function onError(e: { message: string; file?: File }) {
        pushValidationErrorRow(rows, id, e);
      }

      function remove(r: DialogFileRow) {
        rows.value = rows.value.filter((x) => x.id !== r.id);
      }

      return { rows, addFiles, onError, remove, dialogOpen, args };
    },
    template: `
      <UiDialog v-model:open="dialogOpen">
        <UiDialogTrigger as-child>
          <UiButton variant="outline">Open file upload</UiButton>
        </UiDialogTrigger>
        <UiDialogContent max-width="max-w-lg" class="gap-4">
          <template #header>
            <div class="flex items-start justify-between gap-4">
              <UiDialogTitle class="heading-s">File upload</UiDialogTitle>
              <UiDialogClose as-child>
                <UiButton variant="ghost" class="size-9 shrink-0 p-0" aria-label="Close dialog">
                  <UiIcon name="x" :size="20" />
                </UiButton>
              </UiDialogClose>
            </div>
          </template>

          <div class="flex flex-col gap-4">
            <UiFileUpload v-bind="args" @update:files="addFiles" @error="onError" />
            <ul v-if="rows.length" class="flex flex-col gap-2" aria-label="Selected files">
              <li v-for="r in rows" :key="r.id">
                <UiFileItem
                  :filename="r.name"
                  :type-label="r.typeLabel || undefined"
                  :size-label="r.sizeLabel || undefined"
                  :progress="r.progress"
                  :error="r.error"
                  @remove="remove(r)"
                />
              </li>
            </ul>
          </div>

          <template #actions>
            <UiDialogClose as-child>
              <UiButton type="button" variant="ghost">Cancel</UiButton>
            </UiDialogClose>
            <UiDialogClose as-child>
              <UiButton type="button">Save changes</UiButton>
            </UiDialogClose>
          </template>
        </UiDialogContent>
      </UiDialog>
    `,
  };
}

/**
 * Default integration: **Open file upload** opens a dialog with the drop zone, optional file list, and footer actions.
 * Use **Controls** for `accept`, `multiple`, limits, and labels. Footer: **supportedTypesLabel** (left), **maxSizeLabel** (right; blank = auto from **maxFileSizeBytes**).
 */
export const Default: Story = {
  render: (args) => renderInDialog(args),
  parameters: {
    docs: {
      source: {
        code: defaultDialogTemplateSource,
      },
    },
  },
};

/**
 * Mock upload flow: each valid file shows a **progress** bar until 100%, then the bar **hides** (`progress` cleared) so the row reads as uploaded. Open the dialog and add files to watch it.
 */
export const WithMockUploadProgress: Story = {
  render: (args) => renderInDialogWithMockUpload(args),
  parameters: {
    docs: {
      description: {
        story:
          'Demonstrates loading on **UiFileItem** during a fake upload, then removal of the progress UI after completion.',
      },
    },
  },
};

/** PDF, DOCX, TXT + explicit max size footer. */
export const AcceptPdfDocxTxt: Story = {
  args: {
    accept: '.pdf,.docx,.txt,application/pdf',
    maxFileSizeBytes: 500 * 1024 * 1024,
    supportedTypesLabel: 'Supported: PDF, DOCX, TXT',
  },
  render: (args) => renderInDialog(args),
  parameters: docsSource(
    dialogDocsCode(`accept=".pdf,.docx,.txt,application/pdf"
          :max-file-size-bytes="524288000"
          supported-types-label="Supported: PDF, DOCX, TXT"`),
  ),
};

/** MIME wildcard — any image type the browser reports. */
export const AcceptImages: Story = {
  args: {
    accept: 'image/*',
    supportedTypesLabel: 'Supported: images (PNG, JPEG, GIF, WebP, ...)',
  },
  render: (args) => renderInDialog(args),
  parameters: docsSource(
    dialogDocsCode(`accept="image/*"
          supported-types-label="Supported: images (PNG, JPEG, GIF, WebP, ...)"`),
  ),
};

/** Single MIME, e.g. spreadsheets. */
export const AcceptSpreadsheet: Story = {
  args: {
    accept:
      '.csv,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,text/csv',
    supportedTypesLabel: 'Supported: CSV, XLS, XLSX',
  },
  render: (args) => renderInDialog(args),
  parameters: docsSource(
    dialogDocsCode(`accept=".csv,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,text/csv"
          supported-types-label="Supported: CSV, XLS, XLSX"`),
  ),
};

/** Only one file from picker and drop (first valid file wins per interaction). */
export const SingleFile: Story = {
  args: {
    multiple: false,
    accept: '.pdf',
    supportedTypesLabel: 'Single PDF only',
  },
  render: (args) => renderInDialog(args),
  parameters: docsSource(
    dialogDocsCode(`:multiple="false"
          accept=".pdf"
          supported-types-label="Single PDF only"`),
  ),
};

/** Strict max size (1 MB). */
export const MaxSize1Mb: Story = {
  args: {
    accept: '.pdf,.txt,text/plain,application/pdf',
    maxFileSizeBytes: 1024 * 1024,
    supportedTypesLabel: 'PDF or plain text',
  },
  render: (args) => renderInDialog(args),
  parameters: docsSource(
    dialogDocsCode(`accept=".pdf,.txt,text/plain,application/pdf"
          :max-file-size-bytes="1048576"
          supported-types-label="PDF or plain text"`),
  ),
};

/** Custom max size line copy. */
export const CustomMaxSizeLabel: Story = {
  args: {
    accept: undefined,
    maxFileSizeBytes: 10 * 1024 * 1024,
    maxSizeLabel: 'Attachments must be under 10 MB each',
    supportedTypesLabel: 'Any file type',
  },
  render: (args) => renderInDialog(args),
  parameters: docsSource(
    dialogDocsCode(`:max-file-size-bytes="10485760"
          max-size-label="Attachments must be under 10 MB each"
          supported-types-label="Any file type"`),
  ),
};

/** No `accept` — any type; footer hidden when no hints are set. */
export const AnyTypeNoFooter: Story = {
  render: (args) =>
    renderInDialog({
      ...args,
      accept: undefined,
      maxFileSizeBytes: undefined,
      supportedTypesLabel: '',
      maxSizeLabel: '',
    }),
  parameters: docsSource(dialogDocsCode('')),
};

/** Drop zone and picker disabled; the zone is not keyboard-focusable. */
export const Disabled: Story = {
  args: {
    disabled: true,
    accept: '.pdf',
    supportedTypesLabel: 'PDF (disabled)',
  },
  render: (args) => renderInDialog(args),
  parameters: docsSource(
    dialogDocsCode(`disabled
          accept=".pdf"
          supported-types-label="PDF (disabled)"`),
  ),
};

/** Custom title, button, and accessible name. */
export const CustomLabels: Story = {
  args: {
    dropLabel: 'Add attachments',
    chooseLabel: 'Browse...',
    ariaLabel: 'Attachment upload',
    accept: '.zip,.tar,.gz',
    supportedTypesLabel: 'Archives: ZIP, TAR, GZ',
  },
  render: (args) => renderInDialog(args),
  parameters: docsSource(
    dialogDocsCode(`drop-label="Add attachments"
          choose-label="Browse..."
          aria-label="Attachment upload"
          accept=".zip,.tar,.gz"
          supported-types-label="Archives: ZIP, TAR, GZ"`),
  ),
};
