import { GraphBuilder } from "./model/graph_builder.js";
import { GraphRelationsDialog } from "./graph-relations-dialog.js";
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;

import { GraphPermissionsDialog } from "./graph-permissions-dialog.js";
import { log, t, tf } from "./constants.js";

export default class GraphDashboardV2 extends HandlebarsApplicationMixin(ApplicationV2) {

  static ALLOWED_TABS = ["listGraph", "creationGraph"];
  static DEFAULT_ACTIVE_TAB = "listGraph";

  /**
   * @param {GraphApi} api
   */
  constructor(options = {}) {
    log("GraphDashboardV2.constructor", options)
    super(options);
    this.api = options;
    this.tabGroups ??= {};
    this.tabGroups.primary = options.activeTab ?? GraphDashboardV2.DEFAULT_ACTIVE_TAB;
    this.editingGraph = null;
  }

  /* ------------------------------------------------------------------------ */
  /*  Static application definitions                                          */
  /* ------------------------------------------------------------------------ */
  static TABS = {
    primary: {
      tabs: [
        { id: 'creationGraph' },
        { id: 'listGraph' }
      ],
      initial: 'listGraph',
      labelPrefix: "foundry-graph.Tabs",
    }
  }

  static PARTS = {
    tabs: {
      // Foundry-provided generic template
      template: 'templates/generic/tab-navigation.hbs',
    },
    creationGraph: {
      template: "modules/foundry-graph/templates/creationGraphTab.html",
      scrollable: [''],
    },
    listGraph: {
      template: "modules/foundry-graph/templates/listGraphTab.html",
      scrollable: [''],
    }
  };

  static DEFAULT_OPTIONS = {
    id: "fgraph-dashboard",
    window: {
      resizable: true,
    },
    resizable: true,
    minimizable: true,
    position: {
      width: 600,
      height: 600
    },
    height: "auto",
    classes: ["fgraph", "fgraph-dashboard"],
    actions: {
      onCreateGraph: GraphDashboardV2.onCreateGraph,
      onOpenGraph: GraphDashboardV2.onOpenGraph,
      graphEdit: GraphDashboardV2.graphEdit,
      graphDelete: GraphDashboardV2.graphDelete,
      graphPerms: GraphDashboardV2.graphPerms,
      graphRelations: GraphDashboardV2.onGraphRelations,
      onCancelCreate: GraphDashboardV2.onCancelCreate,
      onImportGraph: GraphDashboardV2.onImportGraph,
      graphReassign: GraphDashboardV2.onReassignGraph,
    }
  };

  get title() {
    return t("Window.DashboardTitle");
  }


  /* ------------------------------------------------------------------------ */
  /*  Context / Data                                                          */
  /* ------------------------------------------------------------------------ */

  _prepareContext() {
    const graphs = this.api.getAccessibleGraphs();
    this._graphTypes = this.api.getGraphTypesArray();
    log(graphs)
    log(this._graphTypes)
    if (this.editingGraph) {
      log("editingGraph", this.editingGraph)
    }
    log(this.editingGraph?.graphType)
    const graph = this.editingGraph ?? GraphDashboardV2.blankGraph();

    // --- Determine which graph type is currently selected ---
    // In edit mode: the graph's own type. In create mode: none by default.
    const selectedGraphType = this.editingGraph?.graphType ?? "";

    // --- Build theme list and selected theme for that type ---
    let selectedGraphThemes = null;
    let selectedTheme = this.editingGraph?.theme ?? "";
    if (selectedGraphType) {
      const metadata = this._graphTypes?.find(g => g.id === selectedGraphType);
      if (metadata && Array.isArray(metadata.themes) && metadata.themes.length > 0) {
        selectedGraphThemes = metadata.themes;

        // If the graph has no theme yet (legacy graphs), default to the first theme
        if (!selectedTheme) {
          selectedTheme = metadata.themes[0].id;
        }
      }
    }

    // Enrich each graph entry with its type icon and name for the list view
    const graphTypeMap = new Map((this._graphTypes ?? []).map(t => [t.id, t]));
    const graphsWithMeta = graphs.map(g => ({
      ...g,
      typeIcon: graphTypeMap.get(g.graphType)?.icon ?? "modules/foundry-graph/img/icons/icon-generic.webp",
      typeName: graphTypeMap.get(g.graphType)?.name ?? g.graphType,
      editRelationsEnabled: graphTypeMap.get(g.graphType)?.editRelationsEnabled ?? true,
    }));

    return {
      title: this.title,
      version: this.api.version,
      is_gm: game.user.isGM,
      graphTypes: this._graphTypes,
      tabs: this._prepareTabs("primary"),
      tab: this.activeTab,
      graph,
      selectedGraphType: this.editingGraph?.graphType,
      selectedGraphThemes,
      selectedTheme,
      graphs: graphsWithMeta
    };
  }

  async _preparePartContext(partId, context) {
    switch (partId) {
      case 'creationGraph':
      case 'listGraph':
        context.tab = context.tabs[partId];
        break;
      default:
    }
    return context;
  }


  static blankGraph() {
    return {
      id: "",
      name: "",
      desc: "",
      graphType: "",
      background: { image: "", width: "", height: "" }
    };
  }

  /** Cancel from Create tab: wipe form state and go back to list */
  static onCancelCreate(event, target) {
    // Clear any edit mode and reset inputs defensively
    this.editingGraph = null;
    this.changeTab("listGraph", "primary");
    this.render(true);
  }

  static async onCreateGraph(event, target) {
    const type = this.element.querySelector("#graph-type-select").value?.trim();
    const metadata = this._graphTypes?.find(g => g.id === type);
    log(metadata)
    if (!metadata) return ui.notifications.warn(t("Notifications.InvalidGraphType"));
    log("in oncreategraph")
    const name = this.element.querySelector("#graph-name").value?.trim();
    const id = this.element.querySelector("#graph-id").value?.trim();
    const desc = this.element.querySelector("#graph-desc").value?.trim();
    const width = this.element.querySelector("#graph-width").value?.trim();
    const height = this.element.querySelector("#graph-height").value?.trim();
    const backgroundImagePath = this.element.querySelector("#graph-background").value?.trim();
    const themeSelect = this.element.querySelector("#graph-theme-select");
    const selectedThemeId = themeSelect?.value?.trim() || null;

    // Determine base background from selected theme or fallback to metadata.background
    let metadataBg = metadata.background || {};
    if (selectedThemeId && Array.isArray(metadata.themes)) {
      const theme = metadata.themes.find(t => t.id === selectedThemeId);
      if (theme) metadataBg = theme;
    }
    let finalBgImg = backgroundImagePath || metadataBg?.image;
    let finalBgWidth, finalBgHeight;

    if (backgroundImagePath && backgroundImagePath !== metadataBg?.image) {
      // User picked a new image. Assume the width/height are for the image.
      finalBgWidth = width;
      finalBgHeight = height;
    } else {
      // User is using the default. Use the metadata dimensions.
      finalBgWidth = metadataBg?.width || width;
      finalBgHeight = metadataBg?.height || height;
    }
    log(metadata)

    let graphToSave;

    if (this.editingGraph) {
      // --- EDIT MODE: preserve all existing graph data (nodes, links, etc.)
      // Only overwrite the metadata fields that the form controls.
      graphToSave = foundry.utils.deepClone(this.editingGraph);
      graphToSave.name = name;
      graphToSave.desc = desc;
      graphToSave.theme = selectedThemeId;
      graphToSave.background = {
        ...graphToSave.background,
        image: finalBgImg,
        width: Number(finalBgWidth) || Number(width) || graphToSave.background?.width || 800,
        height: Number(finalBgHeight) || Number(height) || graphToSave.background?.height || 600,
      };
      log("editingGraph - patched metadata", graphToSave);
    } else {
      // --- CREATE MODE: build a brand-new graph from scratch
      graphToSave = new GraphBuilder({
        id: id,
        name: name,
        desc: desc,
        graphType: type,
        theme: selectedThemeId,
        background: {
          image: finalBgImg,
          width: Number(finalBgWidth) || Number(width) || 800,
          height: Number(finalBgHeight) || Number(height) || 600
        }
      }).build();
      log("newGraph", graphToSave);
    }

    // Save the graph
    await this.api.upsertGraph(graphToSave);
    ui.notifications.info(this.editingGraph ? `Graph ${name} updated.` : `Graph ${name} created.`);
    // Refresh the dashboard / list view
    this.editingGraph = null;
    this.changeTab("listGraph", "primary");
    this.render(true);
  }

  static async graphEdit(event, target) {
    log("graphEdit", event, target)
    const graph = await this.api.getGraph(event.target.dataset.id);
    this.editingGraph = graph;
    this.changeTab("creationGraph", "primary");
    this.render(true);
  }

  static async onOpenGraph(event, target) {
    log(event)
    log(target)
    log(event.target.dataset.id)
    const graphId = event.target.dataset.id;
    log(graphId)
    await this.api.openGraphById(graphId, {
       onCloseCallback: () => this.render(false)
    });
  }


  static async graphDelete(event, target) {
    log(event)
    log(event.target.dataset.id)
    await this.api.deleteGraph(event.target.dataset.id);
    ui.notifications.info(`Graph ${event.target.dataset.id} deleted.`);
    this.render(true);
  }

  static async graphPerms(event, target) {
    log(event)
    const graphId = event.target.dataset.id;
    if (!graphId) return ui.notifications.warn(t("Errors.NoGraphSelectedForExport"));
    const g = await this.api.getGraph(graphId);
    new GraphPermissionsDialog({ graphId: graphId, gName: g.name, permissions: g.permissions }).render(true);
  }

  _onPrintGraph() {
    const svgEl = this.element.querySelector("svg#graph-svg");
    if (!svgEl) return;
    const svgBlob = new Blob([svgEl.outerHTML], { type: "image/svg+xml" });
    const url = URL.createObjectURL(svgBlob);
    window.open(url, "_blank");
  }


  /**
     * Open the dialog to manage relations for a specific graph.
     */
  static async onGraphRelations(event, target) {
    log("onGraphRelations", event, target);
    const graphId = event.target.dataset.id;
    const graph = await this.api.getGraph(graphId);
    if (!graph) return ui.notifications.warn("Graph not found!");

    // Define the callback function to run when the dialog saves
    const onSaveCallback = async (newRelations) => {
      log(`Saving ${newRelations.length} relations for graph ${graphId}`);
      graph.relations = newRelations;
      await this.api.upsertGraph(graph);
      //this.render(true); // Refresh the dashboard
      ui.notifications.info(`Relations for "${graph.name}" updated.`);
    };

    // Create and render the dialog
    // We pass a deepClone of relations so canceling doesn't modify the original
    new GraphRelationsDialog({
      graphId: graphId,
      gName: graph.name,
      relations: foundry.utils.deepClone(graph.relations),
      onSave: onSaveCallback
    }).render(true);
  }

  updateButtonState() {
    log("updateButtonState")
    const name = this.element.querySelector("#graph-name").value?.trim();
    const id = this.element.querySelector("#graph-id").value?.trim();
    const type = this.element.querySelector("#graph-type-select").value?.trim();
    log(this.element.querySelector("#graph-type-select"))
    log(name)
    log(id)
    log(type)
    const allFilled = name && id && type;
    log(this.element.querySelector("#create-graph-btn"))
    this.element.querySelector("#create-graph-btn").disabled = !allFilled;
  }

  /** Added to auto-fill form on graph type selection */
  _onGraphTypeChange(event) {
    // Only autofill if we are in "new graph" mode (no editingGraph)
    if (this.editingGraph) return;

    const typeId = event.target.value;
    const metadata = this._graphTypes?.find(g => g.id === typeId) ?? null;

    const widthInput = this.element.querySelector("#graph-width");
    const heightInput = this.element.querySelector("#graph-height");
    const bgInput = this.element.querySelector("#graph-background");
    const themeSelect = this.element.querySelector("#graph-theme-select");

    // Reset theme select options whenever the graph type changes
    if (themeSelect) {
      themeSelect.innerHTML = "";
    }

    // If no type selected, clear fields and exit
    if (!metadata) {
      if (widthInput) widthInput.value = "";
      if (heightInput) heightInput.value = "";
      if (bgInput) {
        bgInput.value = "";
        const valueDisplay = bgInput.querySelector(".file-picker-value");
        if (valueDisplay) valueDisplay.textContent = "";
      }
      this.updateButtonState();
      return;
    }

    const themes = Array.isArray(metadata.themes) ? metadata.themes : [];
    let themeBg = metadata.background || {};
    let defaultThemeId = "";

    if (themes.length > 0) {
      const t0 = themes[0];
      themeBg = t0;
      defaultThemeId = t0.id;
    }

    // Populate theme select with the themes belonging to this graph type
    if (themeSelect) {
      if (themes.length === 0) {
        const opt = document.createElement("option");
        opt.value = "";
        opt.textContent = game.i18n?.localize?.("foundry-graph.themes.none") ?? "Default";
        themeSelect.appendChild(opt);
        themeSelect.value = "";
      } else {
        for (const theme of themes) {
          const opt = document.createElement("option");
          opt.value = theme.id;
          opt.textContent = theme.label || theme.id;
          themeSelect.appendChild(opt);
        }
        themeSelect.value = defaultThemeId;
      }
    }

    // Autofill width/height/background from the chosen theme (or fallback background)
    if (themeBg.width && widthInput) widthInput.value = themeBg.width;
    if (themeBg.height && heightInput) heightInput.value = themeBg.height;
    if (themeBg.image && bgInput) {
      bgInput.value = themeBg.image;
      const valueDisplay = bgInput.querySelector(".file-picker-value");
      if (valueDisplay) valueDisplay.textContent = themeBg.image;
    }

    this.updateButtonState(); // Make sure save button updates
  }

  static onThemeChange(event) {
    //  if (this.editingGraph) return; // only for new graphs

    const typeId = this.element.querySelector("#graph-type-select")?.value;
    if (!typeId) return;
    const metadata = this._graphTypes?.find(g => g.id === typeId);
    if (!metadata || !Array.isArray(metadata.themes)) return;

    const selectedThemeId = event.target.value;
    const theme = metadata.themes.find(t => t.id === selectedThemeId);
    if (!theme) return;

    const widthInput = this.element.querySelector("#graph-width");
    const heightInput = this.element.querySelector("#graph-height");
    const bgInput = this.element.querySelector("#graph-background");

    if (theme.width && widthInput) widthInput.value = theme.width;
    if (theme.height && heightInput) heightInput.value = theme.height;
    if (theme.image && bgInput) {
      bgInput.value = theme.image;
      const valueDisplay = bgInput.querySelector(".file-picker-value");
      if (valueDisplay) valueDisplay.textContent = theme.image;
    }

    this.updateButtonState();
  }

  static async onImportGraph(event, target) {
    // Trigger the hidden file picker
    const fileInput = this.element.querySelector("#import-graph-file");
    if (!fileInput) return;

    // Remove any previous listener to avoid stacking
    fileInput.onchange = null;

    fileInput.onchange = async (e) => {
      const file = e.target.files?.[0];
      if (!file) return;
      fileInput.value = ""; // reset so the same file can be re-imported if needed

      let graph;
      try {
        const text = await file.text();
        graph = JSON.parse(text);
      } catch (err) {
        return ui.notifications.error(t("Errors.ImportParseFailed"));
      }

      // Basic structural validation
      if (!graph?.id || !graph?.graphType) {
        return ui.notifications.error(t("Errors.ImportInvalidFile"));
      }

      const api = game.modules.get("foundry-graph").api;

      // Check for ID collision and reassign if needed
      const existingGraphs = api.getAllGraphs();
      const idCollision = existingGraphs.some(g => g.id === graph.id);
      if (idCollision) {
        // Give it a fresh ID derived from the name to avoid overwriting an existing graph
        const newId = `${graph.id}-imported-${Date.now()}`;
        ui.notifications.warn(
          tf("Notifications.ImportIdCollision", { oldId: graph.id, newId })
        );
        graph.id = newId;
      }

      // Run migrations to ensure compatibility with current schema
      try {
        const migratedGraph = api.migrateGraph
          ? api.migrateGraph(graph)          // if you expose it on the api
          : graph;                           // fallback: trust the file as-is
        await api.upsertGraph(migratedGraph);
      } catch (err) {
        log("Import failed", err);
        return ui.notifications.error(t("Errors.ImportSaveFailed"));
      }

      ui.notifications.info(tf("Notifications.ImportSuccess", { name: graph.name }));
      // Switch to list tab so the user sees their newly imported graph
      this.changeTab("listGraph", "primary");
      this.render(true);
    };

    fileInput.click();
  }

  /**
   * Batch entity-reassignment for imported graphs.
   *
   * 1. Collects every entity whose UUID can no longer be resolved.
   * 2. Attempts an exact-name search inside the current world's collections.
   * 3. Presents a confirmation dialog showing auto-matches and unmatched items.
   * 4. On confirmation, applies the UUID substitutions and saves the graph.
   */
  static async onReassignGraph(event, _target) {
    const graphId = event.target.dataset.id;
    const api = game.modules.get("foundry-graph").api;
    const graph = await api.getGraph(graphId);
    if (!graph) return ui.notifications.warn(t("Errors.GraphNotFound") ?? "Graph not found.");

    const renderer = api.getRenderer(graph.renderer ?? graph.graphType);
    if (!renderer || typeof renderer.getNonExistentEntities !== "function") {
      return ui.notifications.warn("This graph type does not support entity reassignment.");
    }

    const graphData = foundry.utils.deepClone(graph.data);
    const missing = await renderer.getNonExistentEntities(graphData);

    if (missing.length === 0) {
      return ui.notifications.info(`All entities in "${graph.name}" are valid in this world.`);
    }

    // --- Try exact-name search in the appropriate world collection ---
    const matched = [];
    const unmatched = [];
    for (const entry of missing) {
      const found = GraphDashboardV2._findEntityByTypeAndName(entry.entityType, entry.label);
      if (found) {
        matched.push({ oldUuid: entry.uuid, newUuid: found.uuid, oldName: entry.label, newName: found.name, entityType: entry.entityType });
      } else {
        unmatched.push(entry);
      }
    }

    // --- Build dialog HTML ---
    let html = '<div class="fg-reassign-dialog" style="max-height:400px;overflow-y:auto;">';

    if (matched.length > 0) {
      html += `<h3>Auto-matched (${matched.length})</h3>`;
      html += '<table style="width:100%;border-collapse:collapse;margin-bottom:8px;">'
        + '<thead><tr><th style="text-align:left;padding:2px 6px;">Type</th>'
        + '<th style="text-align:left;padding:2px 6px;">Old name</th>'
        + '<th style="padding:2px 4px;">→</th>'
        + '<th style="text-align:left;padding:2px 6px;">New entity</th></tr></thead><tbody>';
      for (const m of matched) {
        html += `<tr><td style="padding:2px 6px;">${m.entityType}</td>`
          + `<td style="padding:2px 6px;">${m.oldName}</td>`
          + `<td style="padding:2px 4px;text-align:center;">→</td>`
          + `<td style="padding:2px 6px;">${m.newName}</td></tr>`;
      }
      html += '</tbody></table>';
    }

    if (unmatched.length > 0) {
      html += `<h3>No match found (${unmatched.length})</h3>`;
      html += '<table style="width:100%;border-collapse:collapse;">'
        + '<thead><tr><th style="text-align:left;padding:2px 6px;">Type</th>'
        + '<th style="text-align:left;padding:2px 6px;">Name</th></tr></thead><tbody>';
      for (const u of unmatched) {
        html += `<tr><td style="padding:2px 6px;">${u.entityType}</td>`
          + `<td style="padding:2px 6px;">${u.label}</td></tr>`;
      }
      html += '</tbody></table>';
    }

    if (matched.length === 0) {
      html += '<p>No automatic matches could be found. The entities above will remain unlinked.</p>';
    }

    html += '</div>';

    const confirmed = await foundry.applications.api.DialogV2.confirm({
      window: { title: `Reassign Entities — ${graph.name}` },
      content: html,
      yes: {
        label: matched.length > 0 ? `Apply ${matched.length} match${matched.length > 1 ? "es" : ""}` : "OK",
        icon: "fa-solid fa-check",
        callback: () => true,
      },
      no: { label: "Cancel", icon: "fa-solid fa-times", callback: () => false },
    });

    if (!confirmed || matched.length === 0) return;

    const updated = renderer.replaceEntities(graphData, matched);
    graph.data = updated;
    await api.upsertGraph(graph);
    const word = matched.length > 1 ? "entities" : "entity";
    ui.notifications.info(`Reassigned ${matched.length} ${word} in "${graph.name}".`);
  }

  /**
   * Searches the current world's Foundry collections for an entity that matches
   * the given type and exact name.
   *
   * @param {string} entityType  - e.g. "Actor", "Item", "Scene", "JournalEntryPage"
   * @param {string} name
   * @returns {foundry.abstract.Document|null}
   */
  static _findEntityByTypeAndName(entityType, name) {
    if (!name) return null;
    switch ((entityType ?? "").toLowerCase()) {
      case "actor":            return game.actors.find(a => a.name === name) ?? null;
      case "item":             return game.items.find(i => i.name === name) ?? null;
      case "scene":            return game.scenes.find(s => s.name === name) ?? null;
      case "journalentrypage": {
        for (const journal of game.journal) {
          const page = journal.pages.find(p => p.name === name);
          if (page) return page;
        }
        return null;
      }
      default:                 return null;
    }
  }

  /** Called after the HTML is rendered */
  _onRender() {
    const graphName = this.element.querySelector('#graph-name')
    log(graphName)
    graphName.addEventListener("change", (e) => {
      e.preventDefault();
      e.stopImmediatePropagation();
      log("event change del nome")
      log(e)
      let newVal = e.target.value
      log(newVal)
      let new_id = newVal.slugify()
      log(new_id)
      this.element.querySelector('#graph-id').value = new_id
      this.updateButtonState();
    })
    // Add listener for graph type dropdown
    const graphTypeSelect = this.element.querySelector('#graph-type-select');
    const themeSelect = this.element.querySelector('#graph-theme-select');

    if (graphTypeSelect) {
      graphTypeSelect.addEventListener("change", (e) => this._onGraphTypeChange(e));
    }

    // Add listener for theme dropdown (call static handler with this bound to the app instance)
    if (themeSelect) {
      themeSelect.addEventListener("change", (e) => {
        this.constructor.onThemeChange.call(this, e);
      });
    }

    this.updateButtonState();
  }


  async _onClose(options) {
    log("GraphDashboardV2._onClose")
    this.editingGraph = null;
    this.changeTab("listGraph", "primary");

    log(this.activeTab, this.editingGraph, options)
    await super._onClose(options);
  }
}