import { geliosProviderPackageV12 } from "../v12/package.mjs";

export const GELIOS_PROVIDER_PACKAGE_ID = "gelios.provider.v13";
export const GELIOS_PROVIDER_PACKAGE_VERSION = "13.0.0";
export const GELIOS_UNIT_CONTACTS_DATA_PRODUCT_ID = "fleet.units.contacts.current.v2";
export const GELIOS_UNIT_CONTACTS_DATA_PRODUCT_VERSION = "2.0.0";
export const GELIOS_UNIT_CONTACTS_ONTOLOGY_REVISION = "ontology.map.moving_object.v3";

const UNITS_CAPABILITY_ID = "gelios.units.identity.read";
const USERS_CAPABILITY_ID = "gelios.users.directory.read";
const FIELD_POLICY_ID = "gelios.units.contacts.fields.v2";
const COLLECTION_PROFILE_ID = "gelios.units.contacts.warm.v2";
const MAPPING_ID = "gelios.units.to.fleet.units.contacts.current.v2";
const TEMPLATE_ID = "gelios.units.contacts.l2.v2";

const contactFields = Object.freeze([
  "available_user_emails",
  "available_user_ids",
  "available_user_legal_names",
  "available_user_logins",
  "available_user_phones",
  "device_imei",
  "device_phone_primary",
  "device_phone_secondary",
  "display_name",
  "provider_creator_email",
  "provider_creator_id",
  "provider_creator_legal_name",
  "provider_creator_login",
  "provider_creator_phone",
]);

const value = structuredClone(geliosProviderPackageV12);
const unitsCapability = value.capabilities.find(
  (capability) => capability.id === UNITS_CAPABILITY_ID,
);
const templateBase = value.l2Templates.find(
  (template) => template.id === "gelios.units.identity.l2.v1",
);
if (!unitsCapability) throw new Error("gelios_v13_units_capability_missing");
if (!templateBase) throw new Error("gelios_v13_template_base_missing");

value.id = GELIOS_PROVIDER_PACKAGE_ID;
value.version = GELIOS_PROVIDER_PACKAGE_VERSION;
value.manifest = {
  ...value.manifest,
  id: "gelios.provider.manifest.v13",
  version: GELIOS_PROVIDER_PACKAGE_VERSION,
  capabilityIds: [...value.manifest.capabilityIds, USERS_CAPABILITY_ID],
  fieldPolicyIds: [...value.manifest.fieldPolicyIds, FIELD_POLICY_ID],
  collectionProfileIds: [...value.manifest.collectionProfileIds, COLLECTION_PROFILE_ID],
  dataProductIds: [...value.manifest.dataProductIds, GELIOS_UNIT_CONTACTS_DATA_PRODUCT_ID],
  mappingContractIds: [...value.manifest.mappingContractIds, MAPPING_ID],
  l2TemplateIds: [...value.manifest.l2TemplateIds, TEMPLATE_ID],
};

value.capabilities.push({
  ...structuredClone(unitsCapability),
  id: USERS_CAPABILITY_ID,
  request: {
    ...structuredClone(unitsCapability.request),
    path: "/api/v1/users",
    query: {
      incladmpan: false,
      inclavfun: false,
      inclbillinf: false,
      inclextra: false,
      inclexs: false,
      inclmon: false,
      inclsmtp: false,
      o1: "userIdAsc",
      pl: 500,
      po: 0,
    },
    response: {
      collectionPaths: ["items"],
      pagination: {
        mode: "offset",
        limitParameter: "pl",
        offsetParameter: "po",
        pageSize: 500,
        itemsPath: "items",
        totalPath: "paginationMetadata.totalCount",
        maxPages: 10,
        maxItems: 5000,
        maxResponseBytes: 16 * 1024 * 1024,
      },
    },
  },
});

value.fieldPolicies.push({
  id: FIELD_POLICY_ID,
  version: GELIOS_UNIT_CONTACTS_DATA_PRODUCT_VERSION,
  dataProductId: GELIOS_UNIT_CONTACTS_DATA_PRODUCT_ID,
  targetFields: [...contactFields],
  unknownSourceFields: "drop",
  dynamicSourceFields: "drop_until_classified",
  restrictedSourcePaths: [
    "commands",
    "currentUserAccess",
    "customFields",
    "driver",
    "extraInfo",
    "hwDecryptKey",
    "lastMsg",
    "lastSensorsVal",
    "maintenancePlan",
    "preSetCommandGroup",
    "sensors",
    "stationaryLat",
    "stationaryLon",
    "storageInfo",
  ],
});

value.collectionProfiles.push({
  id: COLLECTION_PROFILE_ID,
  version: GELIOS_PROVIDER_PACKAGE_VERSION,
  mode: "realtime",
  schedule: { intervalMs: 5 * 60 * 1000 },
  capabilityIds: [UNITS_CAPABILITY_ID, USERS_CAPABILITY_ID],
  dataProductId: GELIOS_UNIT_CONTACTS_DATA_PRODUCT_ID,
  mappingContractId: MAPPING_ID,
  fieldPolicyId: FIELD_POLICY_ID,
  l2TemplateId: TEMPLATE_ID,
  entityScope: {
    mode: "all_visible_to_credential",
    refresh: "each_collection_run",
    businessEntityFilter: "forbidden",
  },
  batching: { maxFacts: 5000 },
  cardinality: {
    maxCurrentEntities: 5000,
    onExceed: "require_partitioned_data_product",
  },
  retry: { maxAttempts: 4, backoff: "fixed_delay", delayMs: 2000 },
});

value.dataProducts.push({
  id: GELIOS_UNIT_CONTACTS_DATA_PRODUCT_ID,
  version: GELIOS_UNIT_CONTACTS_DATA_PRODUCT_VERSION,
  ontologyRevision: GELIOS_UNIT_CONTACTS_ONTOLOGY_REVISION,
  deliveryMode: "snapshot+patch",
  semanticTypes: ["map.moving_object"],
  fields: [...contactFields],
  fieldContracts: {
    available_user_emails: optional("string_array"),
    available_user_ids: optional("string_array"),
    available_user_legal_names: optional("string_array"),
    available_user_logins: optional("string_array"),
    available_user_phones: optional("string_array"),
    device_imei: optional("string"),
    device_phone_primary: optional("string"),
    device_phone_secondary: optional("string"),
    display_name: { type: "string", required: true },
    provider_creator_email: optional("string"),
    provider_creator_id: optional("string"),
    provider_creator_legal_name: optional("string"),
    provider_creator_login: optional("string"),
    provider_creator_phone: optional("string"),
  },
  history: { mode: "none", retentionDays: 1 },
});

value.mappingContracts.push({
  schemaVersion: "nodedc.semantic-mapping/v1",
  id: MAPPING_ID,
  version: GELIOS_PROVIDER_PACKAGE_VERSION,
  sourceCapabilityId: UNITS_CAPABILITY_ID,
  fieldPolicyId: FIELD_POLICY_ID,
  target: {
    dataProductId: GELIOS_UNIT_CONTACTS_DATA_PRODUCT_ID,
    version: GELIOS_UNIT_CONTACTS_DATA_PRODUCT_VERSION,
    ontologyRevision: GELIOS_UNIT_CONTACTS_ONTOLOGY_REVISION,
    semanticType: "map.moving_object",
  },
  derivations: {
    available_user_emails: responseLookupList("availableToUsers", "email"),
    available_user_ids: boundedStringList("availableToUsers", "id"),
    available_user_legal_names: responseLookupList("availableToUsers", "legalName"),
    available_user_logins: boundedStringList("availableToUsers", "login"),
    available_user_phones: responseLookupList("availableToUsers", "phone"),
    provider_creator_email: responseLookupFirst("creator.id", "email"),
    provider_creator_legal_name: responseLookupFirst("creator.id", "legalName"),
    provider_creator_phone: responseLookupFirst("creator.id", "phone"),
  },
  fact: {
    sourceId: {
      strategy: "first_non_empty",
      paths: ["id", "unit_id", "unitId"],
      coerce: "string",
      prefix: "gelios-unit-",
    },
    semanticType: { constant: "map.moving_object" },
    observedAt: {
      strategy: "first_non_empty",
      paths: ["updatedAt"],
      coerce: "unix_or_iso_timestamp",
      fallback: "collection_received_at",
    },
    attributes: {
      available_user_emails: { derive: "available_user_emails" },
      available_user_ids: { derive: "available_user_ids" },
      available_user_legal_names: { derive: "available_user_legal_names" },
      available_user_logins: { derive: "available_user_logins" },
      available_user_phones: { derive: "available_user_phones" },
      device_imei: stringField(["imei"]),
      device_phone_primary: stringField(["phone"]),
      device_phone_secondary: stringField(["phone2"]),
      display_name: stringField(["name", "unit_name", "title", "label"], false),
      provider_creator_email: { derive: "provider_creator_email", omitIfMissing: true },
      provider_creator_id: stringField(["creator.id"]),
      provider_creator_legal_name: {
        derive: "provider_creator_legal_name",
        omitIfMissing: true,
      },
      provider_creator_login: stringField(["creator.login"]),
      provider_creator_phone: { derive: "provider_creator_phone", omitIfMissing: true },
    },
  },
});

value.l2Templates.push({
  ...structuredClone(templateBase),
  id: TEMPLATE_ID,
  version: GELIOS_PROVIDER_PACKAGE_VERSION,
  steps: [
    { id: "collection.trigger", kind: "collection_trigger", collectionProfileDriven: true },
    { id: "provider.fetch-units", kind: "provider_request", capabilityId: UNITS_CAPABILITY_ID },
    { id: "provider.fetch-users", kind: "provider_request", capabilityId: USERS_CAPABILITY_ID },
    { id: "provider.extract-units", kind: "extract_items", capabilityId: UNITS_CAPABILITY_ID },
    { id: "ontology.map", kind: "semantic_mapping", mappingContractId: MAPPING_ID },
    {
      id: "data-product.publish",
      kind: "data_product_publish",
      dataProductId: GELIOS_UNIT_CONTACTS_DATA_PRODUCT_ID,
      nodeType: "n8n-nodes-ndc.ndcDataProductPublish",
    },
  ],
});

export const geliosProviderPackageV13 = deepFreeze(value);

function optional(type) {
  return { type, required: false };
}

function stringField(paths, omitIfMissing = true) {
  return {
    strategy: "first_non_empty",
    paths,
    coerce: "string",
    ...(omitIfMissing ? { omitIfMissing: true } : {}),
  };
}

function boundedStringList(arrayPath, valuePath) {
  return {
    kind: "bounded_string_list",
    rules: ["array.string_values"],
    default: [],
    parameters: {
      arrayPath,
      valuePath,
      maxItems: 256,
      maxItemLength: 512,
    },
  };
}

function responseLookupList(sourceArrayPath, valuePath) {
  return {
    kind: "bounded_response_lookup",
    rules: ["response.lookup.list"],
    default: [],
    parameters: {
      responseCapabilityId: USERS_CAPABILITY_ID,
      responseCollectionPath: "items",
      responseKeyPath: "id",
      sourceArrayPath,
      sourceKeyPath: "id",
      valuePath,
      resultMode: "list",
      maxResponseItems: 5000,
      maxSourceKeys: 256,
      maxItems: 256,
      maxItemLength: 512,
    },
  };
}

function responseLookupFirst(sourceKeyPath, valuePath) {
  return {
    kind: "bounded_response_lookup",
    rules: ["response.lookup.first"],
    default: "",
    parameters: {
      responseCapabilityId: USERS_CAPABILITY_ID,
      responseCollectionPath: "items",
      responseKeyPath: "id",
      sourceKeyPath,
      valuePath,
      resultMode: "first",
      maxResponseItems: 5000,
      maxSourceKeys: 1,
      maxItems: 1,
      maxItemLength: 512,
    },
  };
}

function deepFreeze(input) {
  if (!input || typeof input !== "object" || Object.isFrozen(input)) return input;
  Object.freeze(input);
  for (const child of Object.values(input)) deepFreeze(child);
  return input;
}
