import { Model } from "@ironcalc/workbook";
import { base64ToBytes, bytesToBase64 } from "./util";

const MAX_WORKBOOKS = 50;

type ModelsMetadata = Record<
  string,
  { name: string; createdAt: number; pinned?: boolean }
>;

function randomUUID(): string {
  try {
    return crypto.randomUUID();
  } catch {
    // Fallback for environments without crypto.randomUUID()
    return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
      const r = (Math.random() * 16) | 0;
      const v = c === "x" ? r : (r & 0x3) | 0x8;
      return v.toString(16);
    });
  }
}

export function updateNameSelectedWorkbook(model: Model, newName: string) {
  const uuid = localStorage.getItem("selected");
  if (uuid) {
    const modelsJson = localStorage.getItem("models");
    if (modelsJson) {
      try {
        const models = JSON.parse(modelsJson);
        if (models[uuid]) {
          models[uuid].name = newName;
        } else {
          models[uuid] = { name: newName, createdAt: Date.now() };
        }
        localStorage.setItem("models", JSON.stringify(models));
      } catch (_e) {
        console.warn("Failed saving new name");
      }
    }
    const modeBytes = model.toBytes();
    localStorage.setItem(uuid, bytesToBase64(modeBytes));
  }
}

export function getModelsMetadata(): ModelsMetadata {
  let modelsJson = localStorage.getItem("models");
  if (!modelsJson) {
    modelsJson = "{}";
  }
  const models = JSON.parse(modelsJson);

  // Migrate old format to new format
  const migratedModels: ModelsMetadata = {};
  for (const [uuid, value] of Object.entries(models)) {
    if (typeof value === "string") {
      // Old format: just the name string
      migratedModels[uuid] = { name: value, createdAt: Date.now() };
    } else if (typeof value === "object" && value !== null && "name" in value) {
      // New format: object with name and createdAt
      migratedModels[uuid] = value as { name: string; createdAt: number };
    }
  }

  // Save migrated data back to localStorage
  if (JSON.stringify(models) !== JSON.stringify(migratedModels)) {
    localStorage.setItem("models", JSON.stringify(migratedModels));
  }

  return migratedModels;
}

// Pick a different name Workbook{N} where N = 1, 2, 3
function getNewName(existingNames: string[]): string {
  const baseName = "Workbook";
  let index = 1;
  while (index < MAX_WORKBOOKS) {
    const name = `${baseName}${index}`;
    index += 1;
    if (!existingNames.includes(name)) {
      return name;
    }
  }
  // FIXME: Too many workbooks?
  return "Workbook-Infinity";
}

export function createModelWithSafeTimezone(name: string): Model {
  try {
    const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
    return new Model(name, "en", tz);
  } catch {
    console.warn("Failed to get timezone, defaulting to UTC");
    return new Model(name, "en", "UTC");
  }
}

export function createNewModel(): Model {
  const models = getModelsMetadata();
  const name = getNewName(Object.values(models).map((m) => m.name));

  const model = createModelWithSafeTimezone(name);
  const uuid = randomUUID();
  localStorage.setItem("selected", uuid);
  localStorage.setItem(uuid, bytesToBase64(model.toBytes()));

  models[uuid] = { name, createdAt: Date.now() };
  localStorage.setItem("models", JSON.stringify(models));
  return model;
}

export function loadSelectedModelFromStorage(): Model | null {
  const uuid = localStorage.getItem("selected");
  if (uuid) {
    // We try to load the selected model
    const modelBytesString = localStorage.getItem(uuid);
    if (modelBytesString) {
      return Model.from_bytes(base64ToBytes(modelBytesString));
    }
  }
  return null;
}

// check if storage is empty
export function isStorageEmpty(): boolean {
  const modelsJson = localStorage.getItem("models");
  if (!modelsJson) {
    return true;
  }
  try {
    const models = JSON.parse(modelsJson);
    return Object.keys(models).length === 0;
  } catch (_e) {
    return true;
  }
}

export function saveSelectedModelInStorage(model: Model) {
  const uuid = localStorage.getItem("selected");
  if (uuid) {
    const modeBytes = model.toBytes();
    localStorage.setItem(uuid, bytesToBase64(modeBytes));
  }
}

export function saveModelToStorage(model: Model) {
  const uuid = randomUUID();
  localStorage.setItem("selected", uuid);
  localStorage.setItem(uuid, bytesToBase64(model.toBytes()));
  let modelsJson = localStorage.getItem("models");
  if (!modelsJson) {
    modelsJson = "{}";
  }
  const models = JSON.parse(modelsJson);
  models[uuid] = { name: model.getName(), createdAt: Date.now() };
  localStorage.setItem("models", JSON.stringify(models));
}

export function selectModelFromStorage(uuid: string): Model | null {
  localStorage.setItem("selected", uuid);
  const modelBytesString = localStorage.getItem(uuid);
  if (modelBytesString) {
    return Model.from_bytes(base64ToBytes(modelBytesString));
  }
  return null;
}

export function getSelectedUuid(): string | null {
  return localStorage.getItem("selected");
}

export function deleteSelectedModel(): Model | null {
  const uuid = localStorage.getItem("selected");
  if (!uuid) {
    return null;
  }
  localStorage.removeItem(uuid);
  const metadata = getModelsMetadata();
  delete metadata[uuid];
  localStorage.setItem("models", JSON.stringify(metadata));
  const uuids = Object.keys(metadata);
  if (uuids.length === 0) {
    return createNewModel();
  }
  return selectModelFromStorage(uuids[0]);
}

export function deleteModelByUuid(uuid: string): Model | null {
  localStorage.removeItem(uuid);
  const metadata = getModelsMetadata();
  delete metadata[uuid];
  localStorage.setItem("models", JSON.stringify(metadata));

  // If this was the selected model, we need to select a different one
  const selectedUuid = localStorage.getItem("selected");
  if (selectedUuid === uuid) {
    const uuids = Object.keys(metadata);
    if (uuids.length === 0) {
      return createNewModel();
    }
    // Find the newest workbook by creation timestamp
    const newestUuid = uuids.reduce((newest, current) => {
      const newestTime = metadata[newest]?.createdAt || 0;
      const currentTime = metadata[current]?.createdAt || 0;
      return currentTime > newestTime ? current : newest;
    });
    return selectModelFromStorage(newestUuid);
  }

  // If it wasn't the selected model, return the currently selected model
  if (selectedUuid) {
    const modelBytesString = localStorage.getItem(selectedUuid);
    if (modelBytesString) {
      return Model.from_bytes(base64ToBytes(modelBytesString));
    }
  }

  // Fallback to creating a new model if no valid selected model
  return createNewModel();
}

export function togglePinWorkbook(uuid: string): void {
  const metadata = getModelsMetadata();
  if (metadata[uuid]) {
    metadata[uuid].pinned = !metadata[uuid].pinned;
    localStorage.setItem("models", JSON.stringify(metadata));
  }
}

export function isWorkbookPinned(uuid: string): boolean {
  const metadata = getModelsMetadata();
  return metadata[uuid]?.pinned || false;
}

export function duplicateModel(uuid: string): Model | null {
  const originalModel = selectModelFromStorage(uuid);
  if (!originalModel) return null;

  const duplicatedModel = Model.from_bytes(originalModel.toBytes());
  const models = getModelsMetadata();
  const originalName = models[uuid]?.name || "Workbook";
  const existingNames = Object.values(models).map((m) => m.name);

  // Find next available number
  let counter = 1;
  let newName = `${originalName} (${counter})`;
  while (existingNames.includes(newName)) {
    counter++;
    newName = `${originalName} (${counter})`;
  }

  duplicatedModel.setName(newName);

  const newUuid = randomUUID();
  localStorage.setItem("selected", newUuid);
  localStorage.setItem(newUuid, bytesToBase64(duplicatedModel.toBytes()));

  models[newUuid] = { name: newName, createdAt: Date.now() };
  localStorage.setItem("models", JSON.stringify(models));

  return duplicatedModel;
}
