import { createApiClient } from "./api.js";
import { toDataURL } from "./qr.js";
import { scanQRFromCamera } from "./scanner.js";

const { createApp, ref, reactive, onMounted, watch } = Vue;

createApp({
  setup() {
    const apiBase = ref(localStorage.getItem("geo_api_base") || "https://tenerife.baby");
    const state = reactive({
      publicKey: "",
      privateKey: "",
      accessToken: "",
      collections: [],
      newCollectionName: "",
      selectedCollectionId: "",
      editingCollectionName: "",
      featuresByCollection: {},
      selectedFeatureId: "",
      newFeatureLon: "",
      newFeatureLat: "",
      newFeatureName: "",
      newAssetName: "",
      newAssetDescription: "",
      selectedAssetFileName: "",
      status: "Ready",
      qrPk: "",
      qrPb: "",
      showPrivateQR: true,
      showPublicQR: false,
      showCameraDialog: false,
      cameraError: "",
      cameraAbortController: null,
    });
    const selectedAssetFile = ref(null);

    let client = createApiClient(apiBase.value);

    const rebuildClient = () => {
      client = createApiClient(apiBase.value);
      localStorage.setItem("geo_api_base", apiBase.value);
      state.status = `API base set to ${apiBase.value}`;
    };

    const refreshQRCodes = async () => {
      if (!state.publicKey) {
        state.qrPk = "";
        state.qrPb = "";
        return;
      }
      try {
        state.qrPb = await toDataURL(state.publicKey);
        if (state.privateKey) {
          state.qrPk = await toDataURL(state.privateKey, { dark: "#7f1d1d" });
        } else {
          state.qrPb = "";
        }
      } catch (err) {
        state.qrPk = "";
        state.qrPb = "";
      }
    };

    const ensureKeys = async () => {
      try {
        const keys = await client.ensureKeysInStorage();
        state.publicKey = keys.publicKey;
        state.privateKey = keys.privateKey;
        state.status = "Keys loaded from localStorage.";
        await refreshQRCodes();
      } catch (err) {
        state.status = err.message;
      }
    };

    const restorePublicKeyFromPrivate = async () => {
      if (!state.privateKey?.trim()) {
        state.status = "Enter private key (pk) first.";
        return;
      }
      try {
        const pb = await client.derivePublicKey(state.privateKey.trim());
        state.publicKey = pb;
        client.importKeys({ publicKey: pb, privateKey: state.privateKey.trim() });
        await refreshQRCodes();
        state.status = "Public key (pb) restored from private key (pk).";
      } catch (err) {
        state.status = err.message;
      }
    };

    const closeCameraDialog = () => {
      state.showCameraDialog = false;
      if (state.cameraAbortController) {
        state.cameraAbortController.abort();
        state.cameraAbortController = null;
      }
      state.cameraError = "";
    };

    const runCameraScan = async () => {
      state.cameraError = "";
      state.cameraAbortController = new AbortController();
      const videoEl = document.getElementById("camera-video");
      if (!videoEl) {
        state.cameraError = "Video element not found.";
        return;
      }
      try {
        const pk = await scanQRFromCamera(videoEl, state.cameraAbortController.signal);
        const pkTrimmed = pk.trim();
        closeCameraDialog();
        state.privateKey = pkTrimmed;
        const pb = await client.derivePublicKey(pkTrimmed);
        state.publicKey = pb;
        client.importKeys({ publicKey: pb, privateKey: pkTrimmed });
        await refreshQRCodes();
        try {
          try {
            await client.registerBySigningServiceKey(pb, pkTrimmed);
          } catch (err) {
            const msg = (err?.message || "").toLowerCase();
            const ignore = msg.includes("already registered") || msg.includes("not configured") ||
              msg.includes("admin_public_key") || msg.includes("409") || msg.includes("conflict");
            if (!ignore) throw err;
          }
          state.accessToken = await client.loginWithSignature(pb, pkTrimmed);
          client.setAccessToken(state.accessToken);
          await listCollections();
          state.status = "Imported pk from camera, restored pb, logged in.";
        } catch (err) {
          state.status = `Keys imported. Login failed: ${err.message}`;
        }
      } catch (err) {
        if (err.name === "AbortError") return;
        state.cameraError = err.message || "Camera access failed.";
      }
    };

    const importPkFromCamera = () => {
      state.showCameraDialog = true;
      Vue.nextTick(() => runCameraScan());
    };

    const register = async () => {
      try {
        await client.registerBySigningServiceKey(state.publicKey, state.privateKey);
        state.status = "Registered. Use Login to authenticate.";
      } catch (err) {
        state.status = err.message;
      }
    };

    const login = async () => {
      try {
        try {
          await client.registerBySigningServiceKey(state.publicKey, state.privateKey);
        } catch (err) {
          const msg = (err?.message || "").toLowerCase();
          const ignore = msg.includes("already registered") || msg.includes("not configured") ||
            msg.includes("admin_public_key") || msg.includes("409") || msg.includes("conflict");
          if (!ignore) throw err;
          // Proceed to login: already registered or registration disabled (invitation flow)
        }
        state.accessToken = await client.loginWithSignature(state.publicKey, state.privateKey);
        client.setAccessToken(state.accessToken);
        await listCollections();
        state.status = "Authenticated.";
      } catch (err) {
        state.status = err.message;
      }
    };

    const listCollections = async () => {
      try {
        client.setAccessToken(state.accessToken);
        const data = await client.listCollections();
        state.collections = data.collections || [];
      } catch (err) {
        state.status = err.message;
      }
    };

    const createCollection = async () => {
      try {
        client.setAccessToken(state.accessToken);
        await client.createCollection(state.newCollectionName);
        state.newCollectionName = "";
        await listCollections();
      } catch (err) {
        state.status = err.message;
      }
    };

    const removeCollection = async (collectionId) => {
      try {
        client.setAccessToken(state.accessToken);
        await client.deleteCollection(collectionId);
        if (state.selectedCollectionId === collectionId) {
          state.selectedCollectionId = "";
        }
        delete state.featuresByCollection[collectionId];
        await listCollections();
      } catch (err) {
        state.status = err.message;
      }
    };

    const listFeatures = async (collectionId) => {
      if (!collectionId) return;
      try {
        client.setAccessToken(state.accessToken);
        const data = await client.listFeatures(collectionId);
        state.featuresByCollection[collectionId] = data.features || [];
        if (
          state.selectedFeatureId &&
          !(state.featuresByCollection[collectionId] || []).some((f) => f.id === state.selectedFeatureId)
        ) {
          state.selectedFeatureId = "";
        }
      } catch (err) {
        state.status = err.message;
      }
    };

    const createFeature = async (collectionId) => {
      if (!collectionId) return;
      const lon = parseFloat(state.newFeatureLon);
      const lat = parseFloat(state.newFeatureLat);
      if (isNaN(lon) || isNaN(lat)) {
        state.status = "Enter valid lon/lat numbers.";
        return;
      }
      if (lon < -180 || lon > 180) {
        state.status = "Longitude must be -180 to 180.";
        return;
      }
      if (lat < -90 || lat > 90) {
        state.status = "Latitude must be -90 to 90.";
        return;
      }
      try {
        client.setAccessToken(state.accessToken);
        await client.createPointFeature(collectionId, lon, lat, {
          name: state.newFeatureName || "Point",
        });
        state.newFeatureLon = "";
        state.newFeatureLat = "";
        state.newFeatureName = "";
        await listFeatures(collectionId);
      } catch (err) {
        state.status = err.message;
      }
    };

    const removeFeature = async (collectionId, featureId) => {
      try {
        client.setAccessToken(state.accessToken);
        await client.deleteFeature(featureId);
        await listFeatures(collectionId);
      } catch (err) {
        state.status = err.message;
      }
    };

    const featuresFor = (collectionId) => state.featuresByCollection[collectionId] ?? [];

    const formatFeature = (f) => {
      const coords = f.geometry?.coordinates;
      const lon = coords?.[0] ?? "—";
      const lat = coords?.[1] ?? "—";
      const name = f.properties?.name ?? f.id ?? "—";
      const assets = Array.isArray(f.properties?.assets) ? f.properties.assets : [];
      return { id: f.id, name, lon, lat, assets };
    };

    const selectFeature = (featureId) => {
      state.selectedFeatureId = featureId;
    };

    const selectedFeature = () => {
      const collectionId = state.selectedCollectionId;
      if (!collectionId || !state.selectedFeatureId) return null;
      return (state.featuresByCollection[collectionId] || []).find((f) => f.id === state.selectedFeatureId) ?? null;
    };

    const fileExtension = (name) => {
      const idx = name.lastIndexOf(".");
      if (idx <= 0) return "";
      return name.slice(idx + 1).toLowerCase();
    };

    const kindFromExtension = (ext) => (ext === "gltf" || ext === "glb" ? "3d" : "image");

    const hashFileSha256 = async (file) => {
      const buffer = await file.arrayBuffer();
      const digest = await crypto.subtle.digest("SHA-256", buffer);
      return Array.from(new Uint8Array(digest))
        .map((b) => b.toString(16).padStart(2, "0"))
        .join("");
    };

    const onAssetFileChange = (event) => {
      const target = event?.target;
      const file = target?.files?.[0] ?? null;
      selectedAssetFile.value = file;
      state.selectedAssetFileName = file ? file.name : "";
    };

    const createAndUploadAsset = async () => {
      const feature = selectedFeature();
      if (!feature) {
        state.status = "Select a feature first.";
        return;
      }
      if (!selectedAssetFile.value) {
        state.status = "Select a file first.";
        return;
      }
      const file = selectedAssetFile.value;
      const ext = fileExtension(file.name);
      if (!ext) {
        state.status = "File extension is required.";
        return;
      }
      try {
        client.setAccessToken(state.accessToken);
        const checksum = await hashFileSha256(file);
        const kind = kindFromExtension(ext);
        const created = await client.createOrLinkAsset({
          featureId: feature.id,
          checksum,
          ext,
          kind,
          mimeType: file.type || "application/octet-stream",
          sizeBytes: file.size,
          name: state.newAssetName || file.name,
          description: state.newAssetDescription,
          isPublic: true,
        });
        const upload = await client.getAssetSignedUploadUrl(created.asset.id, file.type || "application/octet-stream");
        const putRes = await fetch(upload.url, {
          method: upload.method || "PUT",
          headers: file.type ? { "Content-Type": file.type } : undefined,
          body: file,
        });
        if (!putRes.ok) {
          throw new Error(`Upload failed with status ${putRes.status}`);
        }
        state.newAssetName = "";
        state.newAssetDescription = "";
        state.selectedAssetFileName = "";
        selectedAssetFile.value = null;
        await listFeatures(state.selectedCollectionId);
        state.status = "Asset uploaded and linked to feature.";
      } catch (err) {
        state.status = err.message;
      }
    };

    const toggleAssetVisibility = async (asset) => {
      try {
        client.setAccessToken(state.accessToken);
        await client.setAssetVisibility(asset.id, !asset.isPublic);
        await listFeatures(state.selectedCollectionId);
        state.status = `Asset visibility updated: ${asset.id}`;
      } catch (err) {
        state.status = err.message;
      }
    };

    const openAssetLink = (asset) => {
      const absolute = client.resolveRelativeLink(asset.link);
      window.open(absolute, "_blank", "noopener,noreferrer");
    };

    watch(
      () => [state.publicKey, state.privateKey],
      () => refreshQRCodes(),
      { immediate: false }
    );

    watch(
      () => state.selectedCollectionId,
      (id) => {
        if (id) listFeatures(id);
      }
    );

    const selectCollection = (id) => {
      state.selectedCollectionId = id;
      state.selectedFeatureId = "";
      const c = state.collections.find((x) => x.id === id);
      state.editingCollectionName = c ? c.name : "";
    };

    const selectedCollection = () =>
      state.collections.find((c) => c.id === state.selectedCollectionId);

    const updateCollectionName = async (collectionId, newName) => {
      if (!newName?.trim()) return;
      try {
        client.setAccessToken(state.accessToken);
        const updated = await client.updateCollection(collectionId, newName.trim());
        const idx = state.collections.findIndex((c) => c.id === collectionId);
        if (idx >= 0) state.collections[idx] = updated;
        state.editingCollectionName = updated.name;
      } catch (err) {
        state.status = err.message;
      }
    };

    const onCollectionNameBlur = () => {
      const c = selectedCollection();
      if (c && state.editingCollectionName !== c.name) {
        updateCollectionName(c.id, state.editingCollectionName);
      }
    };

    onMounted(async () => {
      await ensureKeys();
    });

    const togglePrivateQR = () => {
      state.showPrivateQR = !state.showPrivateQR;
    };

    const togglePublicQR = () => {
      state.showPublicQR = !state.showPublicQR;
    };

    return {
      apiBase,
      state,
      rebuildClient,
      ensureKeys,
      restorePublicKeyFromPrivate,
      importPkFromCamera,
      closeCameraDialog,
      register,
      login,
      listCollections,
      createCollection,
      removeCollection,
      listFeatures,
      selectCollection,
      selectedCollection,
      updateCollectionName,
      onCollectionNameBlur,
      createFeature,
      removeFeature,
      formatFeature,
      featuresFor,
      selectFeature,
      selectedFeature,
      onAssetFileChange,
      createAndUploadAsset,
      toggleAssetVisibility,
      openAssetLink,
      togglePrivateQR,
      togglePublicQR,
    };
  },
}).use(Vuetify.createVuetify()).mount("#app");
