import { Alpine } from "./alpine";
import { showToast } from "./toast";
import { generateCoverBlob } from "./cover-generator";
import { searchTagSuggestions, type TagSuggestion } from "./tag-dropdown";

function getMediaId(): string {
  const parts = window.location.pathname.split("/");
  return parts[parts.length - 1] || "";
}

function getAuthHeader(): string {
  const token = localStorage.getItem("token");
  return token ? `Bearer ${token}` : "";
}

function showProgressSyncModal(): void {
  const modal = document.getElementById("progress-sync-modal");
  if (modal) modal.classList.remove("hidden");
}

function showNotesModal(): void {
  const modal = document.getElementById("notes-modal");
  if (modal) modal.classList.remove("hidden");
}

function hideProgressSyncModal(): void {
  const modal = document.getElementById("progress-sync-modal");
  if (modal) modal.classList.add("hidden");
}

function hideNotesModal(): void {
  const modal = document.getElementById("notes-modal");
  if (modal) modal.classList.add("hidden");
}

function showReaderPlaceholder(): void {
  showToast("Ebook reader coming soon!", "info");
}

function showMetadataEditor(): void {
  const modal = document.getElementById("metadata-editor-modal");
  if (modal) modal.classList.remove("hidden");
}

function hideMetadataEditor(): void {
  const modal = document.getElementById("metadata-editor-modal");
  if (modal) modal.classList.add("hidden");
}

function collectFormData(editorTags: string[]): Record<string, unknown> {
  const modal = document.getElementById("metadata-editor-modal");
  if (!modal) return {};

  const data: Record<string, unknown> = {};
  const inputs = modal.querySelectorAll<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>(
    "input[name], textarea[name], select[name]",
  );
  for (const el of inputs) {
    if (el.name === "tags") continue;
    if (el.type === "number") {
      const val = parseFloat(el.value);
      data[el.name] = isNaN(val) ? 0 : val;
    } else if (el.type === "checkbox") {
      data[el.name] = (el as HTMLInputElement).checked;
    } else {
      data[el.name] = el.value;
    }
  }

  data.tags = editorTags;

  const contribStr = data.contributors as string;
  if (typeof contribStr === "string") {
    data.contributors = contribStr
      .split(",")
      .map((c: string) => c.trim())
      .filter(Boolean);
  }

  return data;
}

export {
  showReaderPlaceholder,
  showMetadataEditor,
  showProgressSyncModal,
  showNotesModal,
  hideProgressSyncModal,
  hideNotesModal,
};

interface MetadataEditorState {
  openSections: Record<string, boolean>;
  coverGenerating: boolean;
  hasExistingCover: boolean;
  newCoverPreview: string;
  coverFile: Blob | null;
  coverAction: string;
  saving: boolean;
  userRating: number;
  ratingHover: number;
  ratingSaving: boolean;
  conflictId: string;
  conflictWinner: string;
  readSaving: boolean;
  toggleSection(section: string): void;
  showMetadataEditor(): void;
  hideMetadataEditor(): void;
  handleCoverUpload(event: Event): void;
  generateCover(): Promise<void>;
  removeCover(): void;
  saveMetadata(): Promise<void>;
  starFill(i: number): string;
  ratingText(): string;
  setRating(value: number): Promise<void>;
  clearRating(): Promise<void>;
  toggleRead(read: boolean): Promise<void>;
  resolveConflict(conflictId: string, winner: string): Promise<void>;
}

Alpine.data("bookDetail", () => {
  const coverImg = document.querySelector(
    "#metadata-editor-modal img#metadata-cover-preview",
  ) as HTMLImageElement | null;
  const hasCover =
    coverImg?.src &&
    !coverImg.src.includes("placeholder-book.svg");

  const coverPreviewEl = document.querySelector(
    ".aspect-\\[2\\/3\\] img",
  ) as HTMLImageElement | null;
  const coverSrc = coverPreviewEl?.src || "";
  const fileUrl = coverSrc && !coverSrc.includes("placeholder") ? coverSrc : "";

  const initialTags: string[] = [];
  const tagBadges = document.querySelectorAll("#metadata-editor-modal [data-editor-tag]");
  tagBadges.forEach((el) => {
    const tag = el.getAttribute("data-editor-tag");
    if (tag) initialTags.push(tag);
  });

  return {
    openSections: { basic: true } as Record<string, boolean>,
    coverGenerating: false,
    hasExistingCover: !!hasCover,
    newCoverPreview: "",
    coverFile: null as Blob | null,
    coverAction: "keep",
    saving: false,
    userRating: 0,
    ratingHover: 0,
    ratingSaving: false,
    conflictId: "",
    conflictWinner: "",
    readSaving: false,
    editorTags: initialTags,
    tagSearch: "",
    tagSuggestions: [] as TagSuggestion[],
    showTagDropdown: false,
    highlightedTagIndex: -1,

    showReaderPlaceholder,
    showMetadataEditor,
    hideMetadataEditor,
    showNotesModal,
    showProgressSyncModal,
    hideProgressSyncModal,
    hideNotesModal,

    async resolveConflict(conflictId: string, winner: string) {
      try {
        const resp = await fetch(`/api/conflicts/${conflictId}/resolve`, {
          method: "POST",
          headers: {
            Authorization: getAuthHeader(),
            "Content-Type": "application/json",
          },
          body: JSON.stringify({ winner }),
        });

        if (!resp.ok) {
          const err = await resp.json().catch(() => ({}));
          throw new Error(err.error || err.message || "Failed to resolve conflict");
        }

        hideProgressSyncModal();
        showToast("Conflict resolved successfully", "success");
        setTimeout(() => window.location.reload(), 500);
      } catch (e) {
        showToast(
          e instanceof Error ? e.message : "Failed to resolve conflict",
          "error",
        );
      }
    },

    init() {
      const ratingAttr = document.body.getAttribute("data-rating");
      this.userRating = ratingAttr ? parseInt(ratingAttr, 10) || 0 : 0;

      const conflictId = document.body.getAttribute("data-conflict-id");
      const conflictWinner = document.body.getAttribute("data-conflict-winner");
      this.conflictId = conflictId && conflictId !== "null" ? conflictId : "";
      this.conflictWinner =
        conflictWinner && conflictWinner !== "null" ? conflictWinner : "";

      const link = document.getElementById("back-link");
      if (!link) return;
      const storageKey = "book_detail_back";
      if (document.referrer) {
        try {
          const ref = new URL(document.referrer);
          if (
            ref.origin === window.location.origin &&
            !ref.pathname.startsWith("/readers/") &&
            ref.pathname !== window.location.pathname
          ) {
            sessionStorage.setItem(storageKey, ref.pathname + ref.search);
          }
        } catch {}
      } else {
        sessionStorage.removeItem(storageKey);
      }
      const backUrl = sessionStorage.getItem(storageKey) || "/dashboard";
      link.addEventListener("click", (e: Event) => {
        e.preventDefault();
        sessionStorage.removeItem(storageKey);
        window.location.href = backUrl;
      });
    },

    toggleSection(section: string) {
      this.openSections[section] = !this.openSections[section];
    },

    starFill(i: number): string {
      const display = this.ratingHover || this.userRating;
      if (i * 2 <= display) {
        return "color: var(--accent);";
      } else if (i * 2 - 1 === display) {
        return "background: linear-gradient(90deg, var(--accent) 50%, var(--text-secondary) 50%); -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent;";
      }
      return "color: var(--text-secondary);";
    },

    ratingText(): string {
      if (this.userRating === 0) return "(not rated)";
      return `(${(this.userRating / 2).toFixed(1)} / 5)`;
    },

    async setRating(value: number) {
      if (this.ratingSaving) return;
      this.ratingSaving = true;
      const mediaId = getMediaId();
      try {
        const resp = await fetch(`/api/media-items/${mediaId}/rating`, {
          method: "POST",
          headers: {
            Authorization: getAuthHeader(),
            "Content-Type": "application/json",
          },
          body: JSON.stringify({ rating: value }),
        });
        if (!resp.ok) {
          const err = await resp.json().catch(() => ({}));
          throw new Error(err.error || "Failed to save rating");
        }
        this.userRating = value;
        this.ratingHover = 0;
        showToast("Rating saved", "success");
      } catch (e) {
        showToast(
          e instanceof Error ? e.message : "Failed to save rating",
          "error",
        );
      } finally {
        this.ratingSaving = false;
      }
    },

    async clearRating() {
      if (this.ratingSaving) return;
      this.ratingSaving = true;
      const mediaId = getMediaId();
      try {
        const resp = await fetch(`/api/media-items/${mediaId}/rating`, {
          method: "DELETE",
          headers: { Authorization: getAuthHeader() },
        });
        if (!resp.ok) {
          const err = await resp.json().catch(() => ({}));
          throw new Error(err.error || "Failed to clear rating");
        }
        this.userRating = 0;
        this.ratingHover = 0;
        showToast("Rating cleared", "success");
      } catch (e) {
        showToast(
          e instanceof Error ? e.message : "Failed to clear rating",
          "error",
        );
      } finally {
        this.ratingSaving = false;
      }
    },

    async toggleRead(read: boolean) {
      if (this.readSaving) return;
      this.readSaving = true;
      const mediaId = getMediaId();
      try {
        // Clear any active sync conflict first. Resolving arms a 10-minute
        // suppression window so the progress write below does not spawn a new
        // conflict. The winner only needs to be a valid source key; the final
        // read/unread state is set by the progress write that follows.
        if (this.conflictId && this.conflictWinner) {
          const cr = await fetch(
            `/api/conflicts/${this.conflictId}/resolve`,
            {
              method: "POST",
              headers: {
                Authorization: getAuthHeader(),
                "Content-Type": "application/json",
              },
              body: JSON.stringify({ winner: this.conflictWinner }),
            },
          );
          // 400 means it was already resolved - treat as no conflict.
          if (!cr.ok && cr.status !== 400) {
            const err = await cr.json().catch(() => ({}));
            throw new Error(
              err.error || err.message || "Failed to clear sync conflict",
            );
          }
        }

        if (read) {
          // Mark as Read: PUT percentage 1.0. (Cannot PUT 0 to unread - the
          // server silently ignores percentage < 0.005 when progress > 0.01.)
          const resp = await fetch(`/api/media-items/${mediaId}/progress`, {
            method: "PUT",
            headers: {
              Authorization: getAuthHeader(),
              "Content-Type": "application/json",
            },
            body: JSON.stringify({ percentage: 1.0 }),
          });
          if (!resp.ok) {
            const err = await resp.json().catch(() => ({}));
            throw new Error(err.error || "Failed to mark as read");
          }
        } else {
          // Mark as Unread: DELETE the progress row. Notes, highlights and
          // ratings are independent and are NOT affected.
          const resp = await fetch(`/api/media-items/${mediaId}/progress`, {
            method: "DELETE",
            headers: { Authorization: getAuthHeader() },
          });
          if (!resp.ok) {
            const err = await resp.json().catch(() => ({}));
            throw new Error(err.error || "Failed to mark as unread");
          }
        }

        showToast(
          read ? "Marked as read" : "Marked as unread",
          "success",
        );
        setTimeout(() => window.location.reload(), 500);
      } catch (e) {
        showToast(
          e instanceof Error ? e.message : "Failed to update read state",
          "error",
        );
      } finally {
        this.readSaving = false;
      }
    },

    handleCoverUpload(event: Event) {
      const input = event.target as HTMLInputElement;
      const file = input.files?.[0];
      if (!file) return;

      this.coverFile = file;
      this.coverAction = "upload";

      const reader = new FileReader();
      reader.onload = (e) => {
        const preview = document.getElementById(
          "metadata-cover-preview",
        ) as HTMLImageElement;
        if (preview && e.target?.result) {
          preview.src = e.target.result as string;
        }
        this.newCoverPreview = e.target?.result as string;
      };
      reader.readAsDataURL(file);
    },

    async generateCover() {
      this.coverGenerating = true;
      try {
        const formatGroup =
          document
            .querySelector('[data-format-group]')
            ?.getAttribute("data-format-group") || "reflowable";

        const blob = await generateCoverBlob(fileUrl, formatGroup);
        if (!blob) return;

        this.coverFile = blob;
        this.coverAction = "upload";

        const preview = document.getElementById(
          "metadata-cover-preview",
        ) as HTMLImageElement;
        if (preview) {
          preview.src = URL.createObjectURL(blob);
        }
        this.newCoverPreview = URL.createObjectURL(blob);
        showToast("Cover generated successfully", "success");
      } finally {
        this.coverGenerating = false;
      }
    },

    removeCover() {
      this.coverAction = "remove";
      this.coverFile = null;
      this.newCoverPreview = "";
      const preview = document.getElementById(
        "metadata-cover-preview",
      ) as HTMLImageElement;
      if (preview) {
        preview.src = "/static/placeholder-book.svg";
      }
    },

    async saveMetadata() {
      if (this.saving) return;
      this.saving = true;

      try {
        const mediaId = getMediaId();
        const formData = collectFormData(this.editorTags);
        formData.cover_action = this.coverAction;

        if (this.coverFile) {
          const fd = new FormData();
          for (const [key, value] of Object.entries(formData)) {
            if (Array.isArray(value)) {
              fd.append(key, JSON.stringify(value));
            } else if (typeof value === "boolean") {
              fd.append(key, value ? "true" : "false");
            } else {
              fd.append(key, String(value));
            }
          }
          fd.append("cover_file", this.coverFile, "cover.jpg");

          const resp = await fetch(`/api/media-items/${mediaId}`, {
            method: "PUT",
            headers: { Authorization: getAuthHeader() },
            body: fd,
          });

          if (!resp.ok) {
            const err = await resp.json().catch(() => ({}));
            throw new Error(err.error || "Failed to save metadata");
          }
        } else {
          const resp = await fetch(`/api/media-items/${mediaId}`, {
            method: "PUT",
            headers: {
              Authorization: getAuthHeader(),
              "Content-Type": "application/json",
            },
            body: JSON.stringify(formData),
          });

          if (!resp.ok) {
            const err = await resp.json().catch(() => ({}));
            throw new Error(err.error || "Failed to save metadata");
          }
        }

        hideMetadataEditor();
        showToast("Metadata saved successfully", "success");
        setTimeout(() => window.location.reload(), 500);
      } catch (e) {
        showToast(
          e instanceof Error ? e.message : "Failed to save metadata",
          "error",
        );
      } finally {
        this.saving = false;
      }
    },

    async searchEditorTags() {
      const libraryId = document.body.getAttribute("data-library-id") || "";
      if (!this.tagSearch || this.tagSearch.length < 2 || !libraryId) {
        this.tagSuggestions = [];
        this.showTagDropdown = false;
        return;
      }
      const results = await searchTagSuggestions(this.tagSearch, libraryId);
      const current = this.editorTags.map((t: string) => t.toLowerCase());
      this.tagSuggestions = results.filter((r: TagSuggestion) => !current.includes(r.value.toLowerCase()));
      this.showTagDropdown = this.tagSuggestions.length > 0;
      this.highlightedTagIndex = -1;
    },

    addEditorTag(tag: string) {
      const trimmed = tag.trim();
      if (!trimmed) return;
      const lower = trimmed.toLowerCase();
      if (this.editorTags.some((t: string) => t.toLowerCase() === lower)) return;
      this.editorTags.push(trimmed);
      this.tagSearch = "";
      this.showTagDropdown = false;
      this.tagSuggestions = [];
      this.highlightedTagIndex = -1;
    },

    removeEditorTag(index: number) {
      this.editorTags.splice(index, 1);
    },

    selectEditorTagSuggestion(tag: string) {
      this.addEditorTag(tag);
    },

    hideEditorTagDropdown() {
      setTimeout(() => { this.showTagDropdown = false; }, 200);
    },

    onTagKeydown(event: KeyboardEvent) {
      if (event.key === "ArrowDown") {
        event.preventDefault();
        if (this.showTagDropdown && this.tagSuggestions.length > 0) {
          this.highlightedTagIndex = (this.highlightedTagIndex + 1) % this.tagSuggestions.length;
        }
      } else if (event.key === "ArrowUp") {
        event.preventDefault();
        if (this.showTagDropdown && this.tagSuggestions.length > 0) {
          this.highlightedTagIndex = this.highlightedTagIndex <= 0
            ? this.tagSuggestions.length - 1
            : this.highlightedTagIndex - 1;
        }
      } else if (event.key === "Enter") {
        event.preventDefault();
        if (this.highlightedTagIndex >= 0 && this.showTagDropdown) {
          this.addEditorTag(this.tagSuggestions[this.highlightedTagIndex].value);
        } else if (this.tagSearch.trim()) {
          this.addEditorTag(this.tagSearch);
        }
      } else if (event.key === ",") {
        event.preventDefault();
        if (this.tagSearch.trim()) {
          this.addEditorTag(this.tagSearch);
        }
      } else if (event.key === "Escape") {
        this.showTagDropdown = false;
        this.tagSuggestions = [];
        this.highlightedTagIndex = -1;
      }
    },
  } as MetadataEditorState;
});
