import { geliosProviderPackageV7 } from "../v7/package.mjs";

export const GELIOS_PROVIDER_PACKAGE_ID = "gelios.provider.v8";
export const GELIOS_PROVIDER_PACKAGE_VERSION = "8.0.0";
export const GELIOS_UNIT_PROFILE_DATA_PRODUCT_ID = "fleet.units.profile.current.v1";
export const GELIOS_UNIT_PROFILE_DATA_PRODUCT_VERSION = "1.0.0";
export const GELIOS_UNIT_PROFILE_ONTOLOGY_REVISION = "ontology.map.moving_object.v3";

const FIELD_POLICY_ID = "gelios.units.profile.fields.v1";
const COLLECTION_PROFILE_ID = "gelios.units.profile.cold.v1";
const MAPPING_ID = "gelios.units.to.fleet.units.profile.current.v1";
const TEMPLATE_ID = "gelios.units.profile.l2.v1";

const profileFields = Object.freeze([
  "corrected_engine_hours_factor",
  "corrected_mileage_factor",
  "display_name",
  "filter_by_satellite_count_enabled",
  "filter_by_satellite_count_value",
  "filter_emissions",
  "hardware_manufacturer_name",
  "hardware_port",
  "hardware_type_class",
  "hardware_type_name",
  "limit_acceleration",
  "lost_connection_enabled",
  "lost_connection_time_value",
  "maximum_permissible_speed",
  "maximum_valid_height",
  "maximum_valid_speed",
  "mileage_by_ignition",
  "minimum_movement_speed",
  "minimum_movement_time",
  "minimum_parking_time",
  "minimum_stop_time",
  "minimum_trip_distance",
  "minimum_valid_height",
  "speed_parameter",
  "trip_detection_type",
  "unit_type_class",
  "unit_type_name",
  "use_odometer",
]);

const fieldContracts = Object.freeze({
  corrected_engine_hours_factor: optional("number"),
  corrected_mileage_factor: optional("number"),
  display_name: { type: "string", required: true },
  filter_by_satellite_count_enabled: optional("boolean"),
  filter_by_satellite_count_value: nonNegative(),
  filter_emissions: nonNegative(),
  hardware_manufacturer_name: optional("string"),
  hardware_port: nonNegative(),
  hardware_type_class: optional("string"),
  hardware_type_name: optional("string"),
  limit_acceleration: nonNegative(),
  lost_connection_enabled: optional("boolean"),
  lost_connection_time_value: nonNegative(),
  maximum_permissible_speed: nonNegative(),
  maximum_valid_height: optional("number"),
  maximum_valid_speed: nonNegative(),
  mileage_by_ignition: optional("boolean"),
  minimum_movement_speed: nonNegative(),
  minimum_movement_time: nonNegative(),
  minimum_parking_time: nonNegative(),
  minimum_stop_time: nonNegative(),
  minimum_trip_distance: nonNegative(),
  minimum_valid_height: optional("number"),
  speed_parameter: optional("string"),
  trip_detection_type: optional("string"),
  unit_type_class: optional("string"),
  unit_type_name: optional("string"),
  use_odometer: optional("boolean"),
});

const value = structuredClone(geliosProviderPackageV7);
value.id = GELIOS_PROVIDER_PACKAGE_ID;
value.version = GELIOS_PROVIDER_PACKAGE_VERSION;
value.manifest = {
  ...value.manifest,
  id: "gelios.provider.manifest.v8",
  version: GELIOS_PROVIDER_PACKAGE_VERSION,
  fieldPolicyIds: [...value.manifest.fieldPolicyIds, FIELD_POLICY_ID],
  collectionProfileIds: [...value.manifest.collectionProfileIds, COLLECTION_PROFILE_ID],
  dataProductIds: [...value.manifest.dataProductIds, GELIOS_UNIT_PROFILE_DATA_PRODUCT_ID],
  mappingContractIds: [...value.manifest.mappingContractIds, MAPPING_ID],
  l2TemplateIds: [...value.manifest.l2TemplateIds, TEMPLATE_ID],
};

value.fieldPolicies.push({
  id: FIELD_POLICY_ID,
  version: GELIOS_UNIT_PROFILE_DATA_PRODUCT_VERSION,
  dataProductId: GELIOS_UNIT_PROFILE_DATA_PRODUCT_ID,
  targetFields: [...profileFields],
  unknownSourceFields: "drop",
  dynamicSourceFields: "drop_until_classified",
  restrictedSourcePaths: [
    "availableToUsers",
    "commands",
    "creator",
    "currentUserAccess",
    "customFields",
    "driver",
    "extraInfo.autocompleteParams",
    "extraInfo.numberPlate",
    "extraInfo.vin",
    "hwDecryptKey",
    "imei",
    "lastMsg.address",
    "lastMsg.params",
    "lastSensorsVal",
    "phone",
    "phone2",
    "preSetCommandGroup",
    "sensors",
    "stationaryLat",
    "stationaryLon",
  ],
});

value.collectionProfiles.push({
  id: COLLECTION_PROFILE_ID,
  version: GELIOS_PROVIDER_PACKAGE_VERSION,
  mode: "realtime",
  schedule: { intervalMs: 6 * 60 * 60 * 1000 },
  capabilityIds: ["gelios.units.current.read"],
  dataProductId: GELIOS_UNIT_PROFILE_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_PROFILE_DATA_PRODUCT_ID,
  version: GELIOS_UNIT_PROFILE_DATA_PRODUCT_VERSION,
  ontologyRevision: GELIOS_UNIT_PROFILE_ONTOLOGY_REVISION,
  deliveryMode: "snapshot+patch",
  semanticTypes: ["map.moving_object"],
  fields: [...profileFields],
  fieldContracts,
  history: { mode: "none", retentionDays: 1 },
});

value.mappingContracts.push({
  schemaVersion: "nodedc.semantic-mapping/v1",
  id: MAPPING_ID,
  version: GELIOS_PROVIDER_PACKAGE_VERSION,
  sourceCapabilityId: "gelios.units.current.read",
  fieldPolicyId: FIELD_POLICY_ID,
  target: {
    dataProductId: GELIOS_UNIT_PROFILE_DATA_PRODUCT_ID,
    version: GELIOS_UNIT_PROFILE_DATA_PRODUCT_VERSION,
    ontologyRevision: GELIOS_UNIT_PROFILE_ONTOLOGY_REVISION,
    semanticType: "map.moving_object",
  },
  derivations: {},
  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: {
      corrected_engine_hours_factor: numberField(["tripParams.correctedEngineHoursFactor"]),
      corrected_mileage_factor: numberField(["tripParams.correctedMileageFactor"]),
      display_name: stringField(["name", "unit_name", "title", "label"], false),
      filter_by_satellite_count_enabled: booleanField(["tripParams.filterBySatellitesCountIsOn"]),
      filter_by_satellite_count_value: nonNegativeField(["tripParams.filterBySatellitesCountValue"]),
      filter_emissions: nonNegativeField(["tripParams.filterEmissions"]),
      hardware_manufacturer_name: stringField(["hwManufacturer.name"]),
      hardware_port: nonNegativeField(["hwType.port"]),
      hardware_type_class: stringField(["hwType.type"]),
      hardware_type_name: stringField(["hwType.name"]),
      limit_acceleration: nonNegativeField(["tripParams.limitAcceleration"]),
      lost_connection_enabled: booleanField(["tripParams.lostConnectionIsOn"]),
      lost_connection_time_value: nonNegativeField(["tripParams.lostConnectionTimeValue"]),
      maximum_permissible_speed: nonNegativeField(["tripParams.maximumPermissibleSpeed"]),
      maximum_valid_height: numberField(["tripParams.maximumValidHeight"]),
      maximum_valid_speed: nonNegativeField(["tripParams.maximumValidSpeed"]),
      mileage_by_ignition: booleanField(["tripParams.mileageByIgnition"]),
      minimum_movement_speed: nonNegativeField(["tripParams.minimumMovementSpeed"]),
      minimum_movement_time: nonNegativeField(["tripParams.minimumMovementTime"]),
      minimum_parking_time: nonNegativeField(["tripParams.minimumParkingTime"]),
      minimum_stop_time: nonNegativeField(["tripParams.minimumTimeStop"]),
      minimum_trip_distance: nonNegativeField(["tripParams.minimumTripDistance"]),
      minimum_valid_height: numberField(["tripParams.minimumValidHeight"]),
      speed_parameter: stringField(["tripParams.speedParam"]),
      trip_detection_type: stringField(["tripParams.tripDetectionType.name"]),
      unit_type_class: stringField(["unitType.type"]),
      unit_type_name: stringField(["unitType.name"]),
      use_odometer: booleanField(["tripParams.useOdometer"]),
    },
  },
});

const positionsTemplate = value.l2Templates.find((template) => template.id === "gelios.positions.current.l2.v7");
if (!positionsTemplate) throw new Error("gelios_v8_positions_template_missing");
value.l2Templates.push({
  ...structuredClone(positionsTemplate),
  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: "gelios.units.current.read" },
    { id: "provider.extract-units", kind: "extract_items", capabilityId: "gelios.units.current.read" },
    { id: "ontology.map", kind: "semantic_mapping", mappingContractId: MAPPING_ID },
    {
      id: "data-product.publish",
      kind: "data_product_publish",
      dataProductId: GELIOS_UNIT_PROFILE_DATA_PRODUCT_ID,
      nodeType: "n8n-nodes-ndc.ndcDataProductPublish",
    },
  ],
});

// The target Engine runtime materializes bounded fixed-delay retries exactly.
// v8 therefore avoids promising a backoff algorithm that n8n cannot execute.
value.collectionProfiles = value.collectionProfiles.map((profile) => ({
  ...profile,
  retry: { maxAttempts: 4, backoff: "fixed_delay", delayMs: 2000 },
}));

export const geliosProviderPackageV8 = deepFreeze(value);

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

function nonNegative() {
  return { type: "number", required: false, minimum: 0 };
}

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

function numberField(paths) {
  return { strategy: "first_non_empty", paths, coerce: "number", omitIfMissing: true };
}

function nonNegativeField(paths) {
  return {
    strategy: "first_non_empty",
    paths,
    coerce: "number",
    minimum: 0,
    omitIfMissing: true,
    omitIfInvalid: true,
  };
}

function booleanField(paths) {
  return { strategy: "first_non_empty", paths, coerce: "boolean", omitIfMissing: true };
}

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;
}
