"use client";

import {
  useEffect,
  useId,
  useRef,
  useState,
  type ChangeEvent,
  type CSSProperties,
  type DragEvent,
} from "react";
import { FoxFormLabel } from "../fox-form-label";
import { cx } from "../../utils";
import { FoxButton } from "../fox-button";
import { FoxIconButton } from "../fox-icon-button";
import { FoxInput } from "../fox-input";
import { FoxTextButton } from "../fox-text-button";

export type FoxFileUploadMode = "default" | "area" | "image";

/** 목록에 그릴 파일 한 줄. 업로드 상태는 호출부가 이 모양으로 바꿔서 넘긴다. */
export interface FoxFileItem {
  /** 목록에서 구분하는 키. */
  id: string;
  /** 확장자를 뺀 이름. 길면 말줄임된다. */
  name: string;
  /** 이름 뒤에 붙는 표기 — 시안은 `.PDF (20KB)`. 줄어들지 않는다. */
  meta?: string;
  /** 참이면 줄이 붉어지고 `message`가 아래에 붙는다. */
  error?: boolean;
  message?: string;
  /** image 모드에서 썸네일로 쓸 주소. */
  previewUrl?: string;
}

export interface FoxFileUploadProps {
  mode?: FoxFileUploadMode;
  /** 주면 제어 컴포넌트가 된다. 없으면 고른 파일을 안에서 들고 있는다. */
  files?: FoxFileItem[];
  defaultFiles?: FoxFileItem[];
  /** 목록이 바뀔 때(고르기·삭제) 부른다. */
  onFilesChange?: (files: FoxFileItem[]) => void;
  /** 파일을 고르면 원본 `File`을 그대로 넘긴다 — 업로드는 호출부가 한다. */
  onSelect?: (picked: File[]) => void;
  onRemove?: (id: string) => void;

  accept?: string;
  multiple?: boolean;
  /**
   * `default`·`area`에서 목록을 보여준다. `image`에는 목록이 없다.
   * 넘기지 않으면 `area`이거나 여러 개를 받을 때만 켜진다 — 파일 하나는 칸이 곧 목록이다.
   */
  showList?: boolean;
  /** 끌어다 놓기. `area`는 안내문이 그렇게 말하므로 이 값과 무관하게 항상 켜져 있다. */
  droppable?: boolean;

  label?: string;
  description?: string;
  placeholder?: string;
  areaText?: string;
  selectLabel?: string;
  removeLabel?: string;

  /** `default` 모드 입력칸 폭. 시안은 320px이고 토큰에 없어 여기서 받는다. */
  inputWidth?: string | number;
  /** `image` 모드 썸네일 크기. 시안은 213×120px(16:9)이고 토큰에 없어 여기서 받는다. */
  thumbnailWidth?: string | number;
  thumbnailHeight?: string | number;

  disabled?: boolean;
  /** 참이면 렌더하지 않는다(DOM에 남지 않는다). */
  hidden?: boolean;
  /** 배치 조정용. */
  className?: string;
}

const DEFAULT_AREA_TEXT = "첨부할 파일을 여기에 끌어다 놓거나 파일 선택 버튼을 직접 선택해주세요.";

/* ── 아이콘 ────────────────────────────────────────────────────────────────
   @fox에 아이콘 세트가 없어 시안의 모양을 직접 그린다. 세트가 들어오면 교체할 자리다.
   전부 `currentColor`라 감싼 쪽이 색을 정한다. */

function UploadIcon() {
  return (
    <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.4" aria-hidden="true">
      <path d="M8 10.5V2.4M4.8 5.4L8 2.2l3.2 3.2" strokeLinecap="round" strokeLinejoin="round" />
      <path d="M2.4 10.2v2.6h11.2v-2.6" strokeLinecap="round" />
    </svg>
  );
}

function PaperclipIcon() {
  return (
    <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.4" aria-hidden="true">
      <path
        d="M12.6 7.6l-4.7 4.7a3 3 0 0 1-4.2-4.2l5.4-5.4a2 2 0 0 1 2.8 2.8l-5.4 5.4a1 1 0 0 1-1.4-1.4l4.7-4.7"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
    </svg>
  );
}

function XCircleIcon() {
  return (
    <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.4" aria-hidden="true">
      <circle cx="8" cy="8" r="6.3" />
      <path d="M6 6l4 4M10 6l-4 4" strokeLinecap="round" />
    </svg>
  );
}

function ProhibitIcon() {
  return (
    <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.4" aria-hidden="true">
      <circle cx="8" cy="8" r="6.3" />
      <path d="M3.5 3.5l9 9" strokeLinecap="round" />
    </svg>
  );
}

function CameraPlusIcon() {
  return (
    <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.2" aria-hidden="true">
      <path d="M1.8 5.4h2.6l1-1.6h3.2l1 1.6h4.6v7.4H1.8z" strokeLinejoin="round" />
      <circle cx="8" cy="9.1" r="2.2" />
      <path d="M12.6 1.6v2.6M11.3 2.9h2.6" strokeLinecap="round" />
    </svg>
  );
}

function TrashIcon() {
  return (
    <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.4" aria-hidden="true">
      <path d="M2.6 4.2h10.8M6.4 4.2V2.8h3.2v1.4M4 4.2l.6 8.4h6.8l.6-8.4" strokeLinecap="round" strokeLinejoin="round" />
    </svg>
  );
}

/* ── 값 다루기 ─────────────────────────────────────────────────────────────── */

function formatBytes(bytes: number): string {
  if (bytes < 1024) {
    return `${bytes}B`;
  }
  if (bytes < 1024 * 1024) {
    return `${Math.round(bytes / 1024)}KB`;
  }
  return `${(bytes / 1024 / 1024).toFixed(1)}MB`;
}

/** 비제어로 쓸 때 원본 `File`을 목록 한 줄로 바꾼다. 시안의 `이름` + `.PDF (20KB)` 모양이다. */
function toItem(file: File, previewUrl?: string): FoxFileItem {
  const dot = file.name.lastIndexOf(".");
  const base = dot > 0 ? file.name.slice(0, dot) : file.name;
  const ext = dot > 0 ? file.name.slice(dot + 1).toUpperCase() : "";
  return {
    id: `${file.name}:${file.size}:${file.lastModified}`,
    name: base,
    meta: ext ? `.${ext} (${formatBytes(file.size)})` : `(${formatBytes(file.size)})`,
    previewUrl,
  };
}

function toSize(value: string | number | undefined): string | undefined {
  if (value === undefined) {
    return undefined;
  }
  return typeof value === "number" ? `${value}px` : value;
}

/**
 * @fox 파일 첨부. 세 모드를 한 컴포넌트로 둔다 — 고르는 방식만 다르고 목록은 같기 때문이다.
 *
 * 업로드는 하지 않는다. 파일을 고르면 원본 `File`을 넘길 뿐이고, 진행률·서버 오류를 목록에
 * 어떻게 비출지는 호출부가 `files`로 정한다. `files`를 넘기지 않으면 고른 파일을 안에서
 * 들고 있어 간단한 폼에서는 그대로 쓸 수 있다.
 *
 * ⚠️ 스타일은 이 파일이 import하지 않는다 — 호스트 앱이 `@use "@fox/styles/components"`
 * (또는 개별 파티셜)로 한 번 불러와야 한다.
 */
export function FoxFileUpload({
  mode = "default",
  files,
  defaultFiles,
  onFilesChange,
  onSelect,
  onRemove,
  accept,
  multiple = false,
  showList,
  droppable = false,
  label,
  description,
  placeholder = "파일을 선택해 주세요.",
  areaText = DEFAULT_AREA_TEXT,
  selectLabel = "파일선택",
  removeLabel = "삭제",
  inputWidth = 320,
  thumbnailWidth = 213,
  thumbnailHeight = 120,
  disabled = false,
  hidden = false,
  className,
}: FoxFileUploadProps) {
  const autoId = useId();
  const pickerRef = useRef<HTMLInputElement>(null);
  const objectUrls = useRef<string[]>([]);
  const [inner, setInner] = useState<FoxFileItem[]>(defaultFiles ?? []);
  const [over, setOver] = useState(false);

  // 비제어로 만든 미리보기 주소는 사라질 때 돌려준다.
  useEffect(
    () => () => {
      objectUrls.current.forEach((url) => URL.revokeObjectURL(url));
    },
    []
  );

  if (hidden) {
    return null;
  }

  const current = files ?? inner;
  // area는 안내문이 끌어다 놓으라고 말하므로 언제나 받는다.
  const dropEnabled = !disabled && (mode === "area" || droppable);
  const listVisible = (showList ?? (mode === "area" || multiple)) && mode !== "image";
  const preview = current[0];
  // 시안의 upload=true 모양 — 목록 없이 파일 하나를 들고 있으면 칸이 잠기고 버튼이 삭제로 바뀐다.
  const singleFilled = mode === "default" && !listVisible && current.length === 1;

  const openPicker = () => {
    pickerRef.current?.click();
  };

  const takeFiles = (picked: File[]) => {
    if (!picked.length) {
      return;
    }
    onSelect?.(picked);

    if (files !== undefined) {
      return;
    }
    const made = picked.map((file) => {
      if (!file.type.startsWith("image/")) {
        return toItem(file);
      }
      const url = URL.createObjectURL(file);
      objectUrls.current.push(url);
      return toItem(file, url);
    });
    const next = multiple ? [...inner, ...made] : made.slice(0, 1);
    setInner(next);
    onFilesChange?.(next);
  };

  const handlePicked = (event: ChangeEvent<HTMLInputElement>) => {
    takeFiles(Array.from(event.target.files ?? []));
    // 같은 파일을 다시 골라도 이벤트가 오게 비운다.
    event.target.value = "";
  };

  const handleRemove = (id: string) => {
    onRemove?.(id);
    if (files !== undefined) {
      return;
    }
    const next = inner.filter((item) => item.id !== id);
    setInner(next);
    onFilesChange?.(next);
  };

  const handleDragOver = (event: DragEvent<HTMLDivElement>) => {
    if (!dropEnabled) {
      return;
    }
    event.preventDefault();
    setOver(true);
  };

  const handleDragLeave = () => {
    setOver(false);
  };

  const handleDrop = (event: DragEvent<HTMLDivElement>) => {
    if (!dropEnabled) {
      return;
    }
    event.preventDefault();
    setOver(false);
    takeFiles(Array.from(event.dataTransfer.files ?? []));
  };

  const thumbStyle: CSSProperties = {
    inlineSize: toSize(thumbnailWidth),
    blockSize: toSize(thumbnailHeight),
  };

  const selectButton = (type: "primary" | "secondary") => (
    <FoxButton
      size="md"
      type={type}
      label={selectLabel}
      leadingIcon={<UploadIcon />}
      disabled={disabled}
      onAction={openPicker}
    />
  );

  return (
    <div
      className={cx("fox-file-upload", mode === "area" && "fox-file-upload--area", className)}
      role="group"
      aria-labelledby={label ? `${autoId}-label` : undefined}
      onDragOver={handleDragOver}
      onDragLeave={handleDragLeave}
      onDrop={handleDrop}
    >
      {label && (
        <FoxFormLabel as="span" id={`${autoId}-label`}>
          {label}
        </FoxFormLabel>
      )}

      {/* 파일 고르기는 네이티브 입력이 맡고 화면에는 버튼만 보인다. */}
      <input
        ref={pickerRef}
        type="file"
        accept={accept}
        multiple={multiple}
        disabled={disabled}
        hidden
        onChange={handlePicked}
      />

      {mode === "default" && (
        <div className="fox-file-upload__row">
          <div
            className={cx(
              "fox-file-upload__field",
              !singleFilled && "fox-file-upload__field--empty"
            )}
            style={{ inlineSize: toSize(inputWidth) }}
          >
            <FoxInput
              size="md"
              aria-label={placeholder}
              value={singleFilled ? `${preview.name}${preview.meta ?? ""}` : ""}
              placeholder={placeholder}
              message={description}
              readOnly
              disabled={disabled}
            />
          </div>

          {singleFilled ? (
            <FoxButton
              size="md"
              type="default"
              label={removeLabel}
              leadingIcon={<TrashIcon />}
              disabled={disabled}
              onAction={() => handleRemove(preview.id)}
            />
          ) : (
            selectButton("secondary")
          )}
        </div>
      )}

      {mode === "area" && (
        <div className={cx("fox-file-upload__area", over && "fox-file-upload__area--over")}>
          <p className="fox-file-upload__area-text">{areaText}</p>
          {selectButton("primary")}
        </div>
      )}

      {mode === "image" && (
        <div className="fox-file-upload__image">
          <div
            className={cx(
              "fox-file-upload__thumb",
              preview?.previewUrl && "fox-file-upload__thumb--filled"
            )}
            style={thumbStyle}
          >
            {preview?.previewUrl ? (
              <>
                {/* eslint-disable-next-line @next/next/no-img-element -- next/image를 쓰면 이
                    폴더가 Next 전용이 된다. */}
                <img className="fox-file-upload__thumb-image" src={preview.previewUrl} alt="" />
                <span className="fox-file-upload__thumb-remove">
                  <FoxIconButton
                    size="xsm"
                    type="overlay"
                    label={removeLabel}
                    icon={<TrashIcon />}
                    disabled={disabled}
                    onAction={() => handleRemove(preview.id)}
                  />
                </span>
              </>
            ) : (
              <span className="fox-file-upload__thumb-icon" aria-hidden="true">
                <CameraPlusIcon />
              </span>
            )}
          </div>

          <div className="fox-file-upload__image-side">
            {selectButton("secondary")}
            {description && <p className="fox-file-upload__description">{description}</p>}
          </div>
        </div>
      )}

      {listVisible && current.length > 0 && (
        <ul className="fox-file-upload__list">
          {current.map((item) => (
            <li
              key={item.id}
              className={cx(
                "fox-file-upload__item",
                item.error && "fox-file-upload__item--error"
              )}
            >
              <div className="fox-file-upload__item-main">
                <span className="fox-file-upload__item-title">
                  <span className="fox-file-upload__item-icon" aria-hidden="true">
                    <PaperclipIcon />
                  </span>
                  <span className="fox-file-upload__item-name">{item.name}</span>
                  {item.meta && <span className="fox-file-upload__item-meta">{item.meta}</span>}
                </span>

                <span className="fox-file-upload__item-actions">
                  <FoxTextButton
                    size="md"
                    type="secondary"
                    label={removeLabel}
                    trailingIcon={<XCircleIcon />}
                    disabled={disabled}
                    onAction={() => handleRemove(item.id)}
                  />
                </span>
              </div>

              {item.error && item.message && (
                <p className="fox-file-upload__item-message">
                  <span className="fox-file-upload__item-message-icon" aria-hidden="true">
                    <ProhibitIcon />
                  </span>
                  {item.message}
                </p>
              )}
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}
