.pragma library

/**
 * Version of graphs
 * @constant
 */
const VERSION = 4; //? Bump when some settings changes in graphs structure

/**
 * @typedef {object} CpuGraph
 * @property {number} _v The version of graph
 * @property {"cpu"} type The graph type
 * @property {[number, number]} sizes The graph size ([width, height], -1 = automatic)
 * @property {[string, string, string]} colors The graph colors (ref: usage, clock, temperature)
 * @property {[("usage"|"system"|"user"), ("none"|"classic"|"ecores"), boolean]} sensorsType The sensors type (ref: usage, clock, temperature)
 * @property {[number, number]} thresholds The temperature thresholds
 * @property {"average"|"minimum"|"maximum"} clockAggregator The clock aggregator
 * @property {number} eCoresCount The e-cores count
 * @property {number} fontSize The font size
 */
/**
 * @typedef {object} MemoryGraph
 * @property {number} _v The version of graph
 * @property {"memory"} type The graph type
 * @property {[number, number]} sizes The graph size ([width, height], -1 = automatic)
 * @property {[string, string]} colors The graph colors (ref: color, swap)
 * @property {[("physical"|"physical-percent"|"application"|"application-percent"|"swap"|"swap-percent"), ("none"|"swap"|"swap-percent"|"physical-percent"|"application-percent")]} sensorsType The sensors type (ref: memory, swap)
 * @property {[number, number]} thresholds The usage thresholds
 * @property {number} fontSize The font size
 */
/**
 * @typedef {object} GpuGraph
 * @property {number} _v The version of graph
 * @property {"gpu"} type The graph type
 * @property {[number, number]} sizes The graph size ([width, height], -1 = automatic)
 * @property {[string, string, string]} colors The graph colors (ref: usage, memory, temperature)
 * @property {[("usage"|"memory"|"memory-percent"),("none"|"memory"|"memory-percent"), boolean]} sensorsType The sensors type (ref: usage/memory, memory, temperature)
 * @property {[number, number]} thresholds The temperature thresholds
 * @property {string} device The device index (eg. gpu0, gpu1)
 * @property {number} fontSize The font size
 */
/**
 * @typedef {object} NetworkGraph
 * @property {number} _v The version of graph
 * @property {"network"} type The graph type
 * @property {[number, number]} sizes The graph size ([width, height], -1 = automatic)
 * @property {[string, string]} colors The graph colors (ref: receiving, sending)
 * @property {[boolean, ("kibibyte"|"kilobit|"kilobyte")]} sensorsType The sensors type (ref: swap Rx/Tx, unit)
 * @property {[number, number]} uplimits The uplimit (ref: chart1, chart2)
 * @property {string[]} ignoredInterfaces The ignored network interfaces
 * @property {boolean} icons Show labels icons (↓ / ↑)
 * @property {number} fontSize The font size
 */
/**
 * @typedef {object} DiskGraph
 * @property {number} _v The version of graph
 * @property {"disk"} type The graph type
 * @property {[number, number]} sizes The graph size ([width, height], -1 = automatic)
 * @property {[string, string]} colors The graph colors (ref: read, write)
 * @property {[boolean]} sensorsType The sensors type (ref: swap r/w)
 * @property {[number, number]} uplimits The uplimit (ref: chart1, chart2)
 * @property {string} device The disk id (eg. sda, sdc), it also could be `all`
 * @property {boolean} icons Show labels icons (R / W)
 * @property {number} fontSize The font size
 */

/**
 * @typedef {object} CpuText
 * @property {number} _v The version of graph
 * @property {"cpuText"} type The graph type
 * @property {[number, number]} sizes The graph size ([width, height], -1 = automatic)
 * @property {[string, string]} colors The graph colors (ref: label, usage)
 * @property {("usage"|"system"|"user"|"temperature")} sensorsType The sensors type (ref: usage)
 * @property {string} title The title of graph
 * @property {("always"|"hints"|"never")} titleWhen The option when title is displayed
 * @property {number} fontSize The font size
 */
/**
 * @typedef {object} MemoryText
 * @property {number} _v The version of graph
 * @property {"memoryText"} type The graph type
 * @property {[number, number]} sizes The graph size ([width, height], -1 = automatic)
 * @property {[string, string]} colors The graph colors (ref: label, usage)
 * @property {("physical"|"physical-percent"|"application"|"application-percent")} sensorsType The sensors type (ref: usage)
 * @property {string} title The title of graph
 * @property {("always"|"hints"|"never")} titleWhen The option when title is displayed
 * @property {number} fontSize The font size
 */
/**
 * @typedef {object} GpuText
 * @property {number} _v The version of graph
 * @property {"gpuText"} type The graph type
 * @property {[number, number]} sizes The graph size ([width, height], -1 = automatic)
 * @property {[string, string]} colors The graph colors (ref: label, usage)
 * @property {[("usage"|"memory"|"memory-percent"|"temperature")]} sensorsType The sensors type (ref: usage/memory)
 * @property {string} device The device index (eg. gpu0, gpu1)
 * @property {string} title The title of graph
 * @property {("always"|"hints"|"never")} titleWhen The option when title is displayed
 * @property {number} fontSize The font size
 */
/**
 * @typedef {object} NetworkText
 * @property {number} _v The version of graph
 * @property {"network"} type The graph type
 * @property {[number, number]} sizes The graph size ([width, height], -1 = automatic)
 * @property {[string, string]} colors The graph colors (ref: receiving, sending)
 * @property {[boolean, ("kibibyte"|"kilobit|"kilobyte")]} sensorsType The sensors type (ref: swap Rx/Tx, unit)
 * @property {string[]} ignoredInterfaces The ignored network interfaces
 * @property {boolean} icons Show labels icons (↓ / ↑)
 * @property {string} title The title of graph
 * @property {("always"|"hints"|"never")} titleWhen The option when title is displayed
 * @property {number} fontSize The font size
 */
/**
 * @typedef {object} DiskText
 * @property {number} _v The version of graph
 * @property {"diskText"} type The graph type
 * @property {[number, number]} sizes The graph size ([width, height], -1 = automatic)
 * @property {[string, string]} colors The graph colors (ref: label, usage)
 * @property {("used"|"used-percent")} sensorsType The sensors type (ref: usage)
 * @property {string} device The disk id (eg. sda, sdc), it also could be `all`
 * @property {boolean} icons Show labels icons (R / W)
 * @property {string} title The title of graph
 * @property {("always"|"hints"|"never")} titleWhen The option when title is displayed
 * @property {number} fontSize The font size
 */
/**
 * @typedef {object} Text
 * @property {number} _v The version of graph
 * @property {"text"} type The graph type
 * @property {[number, number]} sizes The graph size ([width, height], -1 = automatic)
 * @property {string} color The text color
 * @property {string} device The text value
 * @property {string} placement The text placement
 * @property {number} fontSize The font size
 */

/** @typedef {CpuGraph|CpuText|MemoryGraph|MemoryText|GpuGraph|NetworkGraph|NetworkText|DiskGraph|DiskText|Text} Graph */
/**
 * @typedef {object} GraphInfo
 * @property {Graph["type"]} type
 * @property {string} name The graph name
 * @property {string} icon The graph icon
 * @property {string} fallbackIcon The fallback of graph icon
 * @property {string} section The graph section
 * @property {string} device The graph device (set to {@link type} if is not "gpu" or "disk" type)
 */

/**
 * Migration registry: version number → migration function
 * Each function receives a graph and mutates it in place
 * @type {Record<number, (graph: Graph) => void>}
 */
const migrations = {
  // V1 -> V2: Add icons to network/disk graphs
  1: (graph) => {
    if (
      (graph.type === "network" || graph.type === "disk") &&
      typeof graph.icons === "undefined"
    ) {
      graph.icons = false;
    }
  },

  // V2 -> V3: Add custom sizes on each graphs
  // - Rename text "size" to "fontSize"
  2: (graph) => {
    if (typeof graph.sizes === "undefined") {
      graph.sizes = [-1, -1];
    }

    if (graph.type === "text" && typeof graph.size !== "undefined") {
      graph.fontSize = graph.size;
      delete graph.size;
    }
  },

  /*
   * V3 -> V4: Improve flexibility
   * Memory: Change "memory-percent" to independent type (application/physical)
   * GPU: Add customization for first line
   * DiskText: allow used in percent
   * [all]Text: allow customize and behavior on how title is show
   * [all]: allow customize font size
   */
  3: (graph) => {
    // Memory
    if (graph.type === "memory" && graph.sensorsType[1] === "memory-percent") {
      const [type] = graph.sensorsType[0].split("-");
      graph.sensorsType = [graph.sensorsType[0], type + "-percent"];
    }

    // GPU
    if (graph.type === "gpu" && graph.sensorsType.length < 3) {
      graph.sensorsType = ["usage", graph.sensorsType[0], graph.sensorsType[1]];
    }
    if (graph.type === "gpuText" && typeof graph.sensorsType !== "undefined") {
      graph.sensorsType = ["usage"];
    }

    // [all]Text
    if (graph.type === "cpuText" && typeof graph.title === "undefined") {
      graph.title = "CPU";
      graph.titleWhen = "always";
    }
    if (graph.type === "memoryText" && typeof graph.title === "undefined") {
      graph.title = "RAM";
      graph.titleWhen = "always";
    }
    if (graph.type === "gpuText" && typeof graph.title === "undefined") {
      graph.title = "GPU";
      graph.titleWhen = "always";
    }
    if (graph.type === "diskText" && typeof graph.title === "undefined") {
      graph.title = "Disk";
      graph.titleWhen = "always";
    }

    // All
    if (typeof graph.fontSize === "undefined") {
      graph.fontSize = -1;
    }
  },
};

/**
 * Parse json string to graph array
 * @param {string} raw The raw graphs value (in json string)
 * @param {boolean} update Update the outdated entries or not
 * @returns {Graph[]} The parsed graph
 */
function parse(raw, update = false) {
  /** @type {Graph[]} */
  const graphs = JSON.parse(raw || "[]");
  if (graphs.length === 0) return [];

  const result = [];
  let anyChanged = false;
  const warnedVersions = new Set();

  for (const graph of graphs) {
    let currentVersion = graph._v ?? 1;
    const originalVersion = currentVersion;

    //  Handle migrations
    if (update) {
      while (currentVersion < VERSION) {
        const migrateFn = migrations[currentVersion];
        if (typeof migrateFn !== "function") {
          // Warn only once per missing migration version
          if (!warnedVersions.has(currentVersion)) {
            console.warn(`⚠ Missing migration for version ${currentVersion}`);
            warnedVersions.add(currentVersion);
          }
          break;
        }
        migrateFn(graph);
        graph._v = ++currentVersion;
      }

      if (!anyChanged && currentVersion !== originalVersion) {
        anyChanged = true;
      }
    } else if (currentVersion !== VERSION) {
      continue; // Skip outdated (when won't apply update)
    }

    result.push(graph);
  }

  // Mark only first as changed if anything was migrated
  if (anyChanged && result.length > 0) {
    result[0]._changed = true;
  }

  return result;
}

/**
 * Stringify the graphs array
 * @param {Graph[]} graphs The graphs array
 * @returns {string} The stringified array
 */
function stringify(graphs) {
  if (graphs.length > 0 && graphs[0]._changed) {
    delete graphs[0]._changed;
  }

  return JSON.stringify(graphs);
}

/**
 * Create new graph with given type and device
 * @param {Graph["type"]} type The graph type
 * @param {string} [device] The device
 * @param {number} [fontSize] The default font size
 * @returns {Graph} The created graph
 */
function create(type, device, fontSize) {
  /** @type {Graph} */
  let item = {
    _v: VERSION,
    type,
    sizes: [-1, -1],
    fontSize: -1,
  };

  // Fill default value
  switch (type) {
    case "cpu":
      item.colors = ["highlightColor", "textColor", "textColor"];
      item.sensorsType = ["usage", "clock", false];
      item.thresholds = [85, 105];
      item.clockAggregator = "average";
      item.eCoresCount = "";
      break;
    case "cpuText":
      item.colors = ["textColor", "highlightColor"];
      item.sensorsType = ["usage"];
      item.title = "CPU";
      item.titleWhen = "always";
      break;
    case "memory":
      item.colors = ["highlightColor", "negativeTextColor"];
      item.sensorsType = ["physical", "swap"];
      item.thresholds = [70, 90];
      break;
    case "memoryText":
      item.colors = ["textColor", "highlightColor"];
      item.sensorsType = ["physical-percent"];
      item.title = "RAM";
      item.titleWhen = "always";
      break;
    case "gpu":
      item.colors = ["highlightColor", "positiveTextColor", "textColor"];
      item.sensorsType = ["memory", false];
      item.thresholds = [80, 90];
      break;
    case "gpuText":
      item.colors = ["textColor", "highlightColor"];
      item.sensorsType = ["usage"];
      item.title = "GPU";
      item.titleWhen = "always";
      break;
    case "network":
      item.colors = ["highlightColor", "positiveTextColor"];
      item.sensorsType = [false, "kibibyte"];
      item.uplimits = [100000, 100000];
      item.ignoredInterfaces = [];
      item.icons = false;
      break;
    case "networkText":
      item.colors = ["highlightColor", "positiveTextColor"];
      item.sensorsType = [false, "kibibyte"];
      item.ignoredInterfaces = [];
      item.icons = true;
      break;
    case "disk":
      item.colors = ["highlightColor", "positiveTextColor"];
      item.sensorsType = [false];
      item.uplimits = [200000, 200000];
      item.icons = false;
      break;
    case "diskText":
      item.colors = ["textColor", "highlightColor"];
      item.sensorsType = ["used-percent"];
      item.icons = false;
      item.title = "Disk";
      item.titleWhen = "always";
      break;
    case "text":
      item.color = "textColor";
      item.device = "Text";
      item.placement = "middle-right";
      item.fontSize = fontSize ?? 10;
      break;
    default:
      throw new Error(`${type} is not valid graph type`);
  }

  // Fill device
  if (
    type === "gpu" ||
    type === "gpuText" ||
    type === "disk" ||
    type === "diskText"
  ) {
    if (!device?.trim()) {
      throw new Error(`Device is required for ${type} graph`);
    }
    item.device = device;
  }

  return item;
}

/**
 * Retrieve graph display informations (name and icon)
 * @param {Graph["type"]} type The graph type
 * @param {function} i18nc The "i18nc" function //? bypass QML limitation
 * @param {string} section The section name
 * @param {string} device The device identifier
 * @param {string} deviceName The device name
 * @returns {GraphInfo} The display informations
 */
function getDisplayInfo(
  type,
  i18nc = (_ = "", def = "") => def,
  section = "",
  device = type,
  deviceName = device,
) {
  /** @type {GraphInfo} */
  let result = {
    type,
    section,
    device,
  };
  switch (type) {
    case "cpu":
      result.name = i18nc("Chart name", "CPU");
      result.icon = "cpu-symbolic";
      result.fallbackIcon = "cpu";
      break;
    case "cpuText":
      result.name = i18nc("Chart name", "CPU (text)");
      result.icon = "cpu-symbolic";
      result.fallbackIcon = "cpu";
      break;
    case "memory":
      result.name = i18nc("Chart name", "Memory");
      result.icon = "memory-symbolic";
      result.fallbackIcon = "memory";
      break;
    case "memoryText":
      result.name = i18nc("Chart name", "Memory (text)");
      result.icon = "memory-symbolic";
      result.fallbackIcon = "memory";
      break;
    case "gpu":
      result.name = i18nc("Chart name", "GPU [%1]", deviceName);
      result.icon = "freon-gpu-temperature-symbolic";
      result.fallbackIcon = "preferences-desktop-display";
      break;
    case "gpuText":
      result.name = i18nc("Chart name", "GPU (text) [%1]", deviceName);
      result.icon = "freon-gpu-temperature-symbolic";
      result.fallbackIcon = "preferences-desktop-display";
      break;
    case "network":
      result.name = i18nc("Chart name", "Network");
      result.icon = "network-wired-symbolic";
      result.fallbackIcon = "network-wired";
      break;
    case "networkText":
      result.name = i18nc("Chart name", "Network (text)");
      result.icon = "network-wired-symbolic";
      result.fallbackIcon = "network-wired";
      break;
    case "disk":
      result.name = i18nc("Chart name", "Disk I/O [%1]", deviceName);
      result.icon = "drive-harddisk-symbolic";
      result.fallbackIcon = "drive-harddisk";
      break;
    case "diskText":
      result.name = i18nc("Chart name", "Disk (text) [%1]", deviceName);
      result.icon = "drive-harddisk-symbolic";
      result.fallbackIcon = "drive-harddisk";
      break;
    case "text":
      result.name = i18nc("Chart name", "Text");
      result.icon = "dialog-text-and-font";
      result.fallbackIcon = "draw-text";
      break;
    default:
      throw new Error(`${type} is not valid graph type`);
  }
  return result;
}
