import { Injectable } from '@angular/core';
import { Metadata, RunView } from '@memberjunction/core';
import { UUIDsEqual } from '@memberjunction/global';
import { IntegrationEngineBase } from '@memberjunction/integration-engine-base';
import { GraphQLIntegrationClient, GraphQLActionClient } from '@memberjunction/graphql-dataprovider';
import * as i0 from "@angular/core";
/** Shared icon resolution map for integration names to Font Awesome icon classes */
const INTEGRATION_ICON_MAP = [
    { Pattern: /hubspot/i, Icon: 'fa-brands fa-hubspot' },
    { Pattern: /salesforce/i, Icon: 'fa-brands fa-salesforce' },
    { Pattern: /google/i, Icon: 'fa-brands fa-google' },
    { Pattern: /microsoft|dynamics|azure/i, Icon: 'fa-brands fa-microsoft' },
    { Pattern: /slack/i, Icon: 'fa-brands fa-slack' },
    { Pattern: /jira|atlassian/i, Icon: 'fa-brands fa-atlassian' },
    { Pattern: /github/i, Icon: 'fa-brands fa-github' },
    { Pattern: /stripe/i, Icon: 'fa-brands fa-stripe' },
    { Pattern: /shopify/i, Icon: 'fa-brands fa-shopify' },
    { Pattern: /mailchimp/i, Icon: 'fa-brands fa-mailchimp' },
    { Pattern: /wordpress/i, Icon: 'fa-brands fa-wordpress' },
    { Pattern: /dropbox/i, Icon: 'fa-brands fa-dropbox' },
    { Pattern: /csv|file|import/i, Icon: 'fa-solid fa-file-csv' },
    { Pattern: /postgres|mysql|sql|database|db/i, Icon: 'fa-solid fa-database' },
    { Pattern: /api|rest|graphql/i, Icon: 'fa-solid fa-code' },
    { Pattern: /member|membership/i, Icon: 'fa-solid fa-users' },
    { Pattern: /email|mail/i, Icon: 'fa-solid fa-envelope' },
    { Pattern: /calendar|event/i, Icon: 'fa-solid fa-calendar' }
];
/**
 * Resolve an integration icon. Checks the entity's Icon field first (supports
 * Font Awesome classes, URLs, and base64), then falls back to pattern-based
 * name matching, then to a generic plug icon.
 */
export function ResolveIntegrationIcon(name, entityIcon) {
    // If the Integration entity has an Icon value that looks like a FA class, use it directly
    if (entityIcon && entityIcon.startsWith('fa-')) {
        return entityIcon;
    }
    // Fall back to pattern-based name matching
    const match = INTEGRATION_ICON_MAP.find(m => m.Pattern.test(name));
    return match ? match.Icon : 'fa-solid fa-plug';
}
/**
 * Check if the Icon field contains an image URL or base64 data URI
 * (as opposed to a Font Awesome class). Used by templates to decide
 * whether to render an <i> or an <img>.
 */
export function IsImageIcon(icon) {
    if (!icon)
        return false;
    return icon.startsWith('http') || icon.startsWith('data:') || icon.startsWith('/');
}
export class IntegrationDataService {
    _provider = null;
    /** Set the metadata provider this service should use. Components should call this after injection. */
    set Provider(value) {
        this._provider = value;
    }
    get Provider() {
        return this._provider ?? Metadata.Provider;
    }
    async LoadIntegrationSummaries(provider) {
        const engine = IntegrationEngineBase.Instance;
        const rv = this.createRunView(provider);
        // Engine provides cached metadata; only runs are dynamic and need a RunView call
        const [, runsResult] = await Promise.all([
            engine.Config(false),
            rv.RunView({
                EntityName: 'MJ: Company Integration Runs',
                ExtraFilter: '',
                OrderBy: 'StartedAt DESC',
                Fields: ['ID', 'CompanyIntegrationID', 'StartedAt', 'EndedAt', 'TotalRecords',
                    'Status', 'ErrorLog', 'Integration', 'Company', 'RunByUser'],
                ResultType: 'simple'
            })
        ]);
        const integrations = engine.CompanyIntegrations;
        const runs = runsResult.Results;
        const sourceTypes = engine.SourceTypes;
        const integrationDefs = engine.Integrations;
        return integrations.map(integration => this.buildSummary(integration, runs, sourceTypes, integrationDefs));
    }
    async LoadEntityMaps(companyIntegrationID, _provider) {
        await IntegrationEngineBase.Instance.Config(false);
        return IntegrationEngineBase.Instance.GetEntityMapsForCompanyIntegration(companyIntegrationID);
    }
    async LoadFieldMaps(entityMapID, _provider) {
        await IntegrationEngineBase.Instance.Config(false);
        return IntegrationEngineBase.Instance.GetFieldMapsForEntityMap(entityMapID);
    }
    async LoadRunHistory(companyIntegrationID, limit = 10, provider) {
        const rv = this.createRunView(provider);
        const result = await rv.RunView({
            EntityName: 'MJ: Company Integration Runs',
            ExtraFilter: `CompanyIntegrationID='${companyIntegrationID}'`,
            OrderBy: 'StartedAt DESC',
            MaxRows: limit,
            Fields: ['ID', 'CompanyIntegrationID', 'StartedAt', 'EndedAt', 'TotalRecords',
                'Status', 'ErrorLog', 'Integration', 'Company', 'RunByUser'],
            ResultType: 'simple'
        });
        return result.Results;
    }
    async LoadRunDetails(runID, provider) {
        const rv = this.createRunView(provider);
        const result = await rv.RunView({
            EntityName: 'MJ: Company Integration Run Details',
            ExtraFilter: `CompanyIntegrationRunID='${runID}'`,
            OrderBy: 'Entity',
            Fields: ['ID', 'CompanyIntegrationRunID', 'EntityID', 'RecordID',
                'Action', 'IsSuccess', 'Entity'],
            ResultType: 'simple'
        });
        return this.aggregateRunDetails(result.Results);
    }
    /** Aggregate raw per-record detail rows into per-entity summary rows */
    aggregateRunDetails(rawRecords) {
        const entityMap = new Map();
        for (const rec of rawRecords) {
            const key = rec.EntityID.toLowerCase();
            let row = entityMap.get(key);
            if (!row) {
                row = {
                    EntityID: rec.EntityID,
                    Entity: rec.Entity,
                    RecordsProcessed: 0,
                    RecordsCreated: 0,
                    RecordsUpdated: 0,
                    RecordsDeleted: 0,
                    RecordsErrored: 0,
                    RecordsSkipped: 0
                };
                entityMap.set(key, row);
            }
            row.RecordsProcessed++;
            if (!rec.IsSuccess) {
                row.RecordsErrored++;
            }
            else {
                const action = (rec.Action ?? '').toUpperCase();
                if (action === 'INSERT' || action === 'CREATE') {
                    row.RecordsCreated++;
                }
                else if (action === 'UPDATE') {
                    row.RecordsUpdated++;
                }
                else if (action === 'DELETE') {
                    row.RecordsDeleted++;
                }
                else if (action === 'SKIP' || action === 'SKIPPED') {
                    row.RecordsSkipped++;
                }
            }
        }
        return Array.from(entityMap.values()).sort((a, b) => a.Entity.localeCompare(b.Entity));
    }
    async LoadIntegrationDefinitions(_provider) {
        await IntegrationEngineBase.Instance.Config(false);
        return IntegrationEngineBase.Instance.Integrations;
    }
    async LoadSourceTypes(_provider) {
        await IntegrationEngineBase.Instance.Config(false);
        return IntegrationEngineBase.Instance.SourceTypes.filter(st => st.Status === 'Active');
    }
    async LoadRecentRuns(limit = 20, provider) {
        const rv = this.createRunView(provider);
        const result = await rv.RunView({
            EntityName: 'MJ: Company Integration Runs',
            ExtraFilter: '',
            OrderBy: 'StartedAt DESC',
            MaxRows: limit,
            Fields: ['ID', 'CompanyIntegrationID', 'StartedAt', 'EndedAt', 'TotalRecords',
                'Status', 'ErrorLog', 'Integration', 'Company', 'RunByUser'],
            ResultType: 'simple'
        });
        return result.Results.map(run => this.buildActivityFeedItem(run));
    }
    async LoadDailyRecordCounts(days = 7, provider) {
        const rv = this.createRunView(provider);
        const cutoffDate = new Date();
        cutoffDate.setDate(cutoffDate.getDate() - days);
        const cutoffStr = cutoffDate.toISOString().split('T')[0];
        const result = await rv.RunView({
            EntityName: 'MJ: Company Integration Runs',
            ExtraFilter: `StartedAt >= '${cutoffStr}'`,
            OrderBy: 'StartedAt ASC',
            Fields: ['ID', 'CompanyIntegrationID', 'StartedAt', 'EndedAt', 'TotalRecords',
                'Status', 'ErrorLog', 'Integration', 'Company', 'RunByUser'],
            ResultType: 'simple'
        });
        return this.aggregateDailyCounts(result.Results, days);
    }
    ComputeKPIs(summaries) {
        const totalIntegrations = summaries.length;
        const activeSyncs = summaries.filter(s => s.LatestRun?.Status === 'In Progress' || s.LatestRun?.Status === 'Pending').length;
        const recordsSyncedToday = summaries.reduce((acc, s) => acc + s.TotalRecordsSyncedToday, 0);
        const totalRuns = summaries.reduce((acc, s) => acc + s.RecentRuns.length, 0);
        const totalErrors = summaries.reduce((acc, s) => acc + s.TotalErrors, 0);
        const errorRate = totalRuns > 0 ? (totalErrors / totalRuns) * 100 : 0;
        const durationsMs = summaries
            .filter(s => s.DurationMs != null)
            .map(s => s.DurationMs);
        const averageSyncDurationMs = durationsMs.length > 0
            ? durationsMs.reduce((a, b) => a + b, 0) / durationsMs.length
            : null;
        return {
            TotalIntegrations: totalIntegrations,
            ActiveSyncs: activeSyncs,
            RecordsSyncedToday: recordsSyncedToday,
            ErrorRate: Math.round(errorRate * 10) / 10,
            AverageSyncDurationMs: averageSyncDurationMs
        };
    }
    FormatDuration(ms) {
        if (ms == null)
            return '--';
        const totalSeconds = Math.floor(ms / 1000);
        if (totalSeconds < 60)
            return `${totalSeconds}s`;
        const minutes = Math.floor(totalSeconds / 60);
        const seconds = totalSeconds % 60;
        if (minutes < 60)
            return `${minutes}m ${seconds}s`;
        const hours = Math.floor(minutes / 60);
        const remainingMinutes = minutes % 60;
        return `${hours}h ${remainingMinutes}m`;
    }
    ComputeRelativeTime(dateStr) {
        return this.computeRelativeTime(dateStr);
    }
    // --- Entity Map CRUD ---
    async CreateEntityMap(params) {
        const md = this.Provider;
        const em = await md.GetEntityObject('MJ: Company Integration Entity Maps');
        em.NewRecord();
        em.CompanyIntegrationID = params.CompanyIntegrationID;
        em.ExternalObjectName = params.ExternalObjectName;
        if (params.ExternalObjectLabel)
            em.ExternalObjectLabel = params.ExternalObjectLabel;
        em.EntityID = params.EntityID;
        em.SyncDirection = params.SyncDirection ?? 'Pull';
        em.ConflictResolution = params.ConflictResolution ?? 'SourceWins';
        em.Priority = params.Priority ?? 0;
        em.DeleteBehavior = params.DeleteBehavior ?? 'SoftDelete';
        em.Status = 'Active';
        em.SyncEnabled = true;
        const saved = await em.Save();
        if (!saved) {
            console.error('[IntegrationDataService] Failed to create entity map:', em.LatestResult?.CompleteMessage);
            return null;
        }
        return em;
    }
    async DeleteEntityMap(entityMapID) {
        const md = this.Provider;
        const em = await md.GetEntityObject('MJ: Company Integration Entity Maps');
        await em.Load(entityMapID);
        return em.Delete();
    }
    async ToggleEntityMapEnabled(entityMapID, enabled) {
        const md = this.Provider;
        const em = await md.GetEntityObject('MJ: Company Integration Entity Maps');
        await em.Load(entityMapID);
        em.SyncEnabled = enabled;
        return em.Save();
    }
    async UpdateSyncDirection(entityMapID, direction) {
        const md = this.Provider;
        const em = await md.GetEntityObject('MJ: Company Integration Entity Maps');
        await em.Load(entityMapID);
        em.SyncDirection = direction;
        return em.Save();
    }
    // --- Field Map CRUD ---
    async CreateFieldMap(params) {
        const md = this.Provider;
        const fm = await md.GetEntityObject('MJ: Company Integration Field Maps');
        fm.NewRecord();
        fm.EntityMapID = params.EntityMapID;
        fm.SourceFieldName = params.SourceFieldName;
        if (params.SourceFieldLabel)
            fm.SourceFieldLabel = params.SourceFieldLabel;
        fm.DestinationFieldName = params.DestinationFieldName;
        if (params.DestinationFieldLabel)
            fm.DestinationFieldLabel = params.DestinationFieldLabel;
        fm.IsKeyField = params.IsKeyField ?? false;
        fm.IsRequired = params.IsRequired ?? false;
        fm.Direction = params.Direction ?? 'SourceToDest';
        if (params.TransformPipeline !== undefined)
            fm.TransformPipeline = params.TransformPipeline;
        fm.Status = 'Active';
        fm.Priority = params.Priority ?? 0;
        const saved = await fm.Save();
        if (!saved) {
            console.error('[IntegrationDataService] Failed to create field map:', fm.LatestResult?.CompleteMessage);
            return null;
        }
        return fm;
    }
    async DeleteFieldMap(fieldMapID) {
        const md = this.Provider;
        const fm = await md.GetEntityObject('MJ: Company Integration Field Maps');
        await fm.Load(fieldMapID);
        return fm.Delete();
    }
    async UpdateFieldMap(fieldMapID, updates) {
        const md = this.Provider;
        const fm = await md.GetEntityObject('MJ: Company Integration Field Maps');
        await fm.Load(fieldMapID);
        if (updates.SourceFieldName !== undefined)
            fm.SourceFieldName = updates.SourceFieldName;
        if (updates.DestinationFieldName !== undefined)
            fm.DestinationFieldName = updates.DestinationFieldName;
        if (updates.IsKeyField !== undefined)
            fm.IsKeyField = updates.IsKeyField;
        if (updates.IsRequired !== undefined)
            fm.IsRequired = updates.IsRequired;
        if (updates.Direction !== undefined)
            fm.Direction = updates.Direction;
        if (updates.Status !== undefined)
            fm.Status = updates.Status;
        if (updates.TransformPipeline !== undefined)
            fm.TransformPipeline = updates.TransformPipeline;
        return fm.Save();
    }
    /** Load available MJ entities for mapping target selection */
    async LoadMJEntities(_provider) {
        const md = this.Provider;
        return md.Entities
            .map(e => ({ ID: e.ID, Name: e.Name }))
            .sort((a, b) => a.Name.localeCompare(b.Name));
    }
    /** Load entity fields for a given entity (for field mapping destination picker) */
    async LoadEntityFields(entityID, _provider) {
        const md = this.Provider;
        const entity = md.Entities.find(e => UUIDsEqual(e.ID, entityID));
        if (!entity)
            return [];
        return entity.Fields
            .sort((a, b) => a.Sequence - b.Sequence)
            .map(f => ({ ID: f.ID, Name: f.Name, Type: f.Type, IsRequired: !f.AllowsNull }));
    }
    // --- Discovery (via GraphQL) ---
    /** Discover external objects available for a company integration */
    async DiscoverObjects(companyIntegrationID) {
        const client = this.getIntegrationClient();
        return client.DiscoverObjects(companyIntegrationID);
    }
    /** Discover fields on a specific external object */
    async DiscoverFields(companyIntegrationID, objectName) {
        const client = this.getIntegrationClient();
        return client.DiscoverFields(companyIntegrationID, objectName);
    }
    /** Generate DDL preview for creating tables from discovered objects */
    async SchemaPreview(companyIntegrationID, objects, platform = 'sqlserver') {
        const client = this.getIntegrationClient();
        return client.SchemaPreview(companyIntegrationID, objects, platform);
    }
    /** Execute the full RSU pipeline for an integration entity map */
    async RunSchemaPipeline(companyIntegrationID, entityMap) {
        const md = this.Provider;
        const entityInfo = md.Entities.find(e => UUIDsEqual(e.ID, entityMap.EntityID));
        if (!entityInfo) {
            return { Success: false, Message: `Entity not found for ID ${entityMap.EntityID}` };
        }
        const objects = [{
                SourceObjectName: entityMap.ExternalObjectName,
                SchemaName: entityInfo.SchemaName,
                TableName: entityInfo.BaseTable,
                EntityName: entityInfo.Name
            }];
        const client = this.getIntegrationClient();
        const result = await client.ApplySchema(companyIntegrationID, objects);
        return { Success: result.Success, Message: result.Message };
    }
    /** Execute the RSU pipeline for new entity maps — introspects, generates DDL, runs full pipeline */
    async ApplySchemaForNewMaps(companyIntegrationID, objects) {
        const client = this.getIntegrationClient();
        const result = await client.ApplySchema(companyIntegrationID, objects);
        return { Success: result.Success, Message: result.Message ?? '' };
    }
    /** Batch apply schema for multiple connectors — one RSU pipeline run */
    async ApplySchemaBatch(items) {
        const client = this.getIntegrationClient();
        return client.ApplySchemaBatch(items);
    }
    /** Batch Apply All: schema + entity maps + field maps + sync via IntegrationApplyAllBatch */
    async ApplyAllBatch(companyIntegrationID, sourceObjects) {
        const client = this.getIntegrationClient();
        return client.ApplyAllBatch([{ CompanyIntegrationID: companyIntegrationID, SourceObjects: sourceObjects }]);
    }
    /**
     * Lists every source object the external system exposes — thin catalog for
     * the picker. No per-object describe; that runs later in ApplyAllBatch.
     */
    async ListSourceObjects(companyIntegrationID) {
        const client = this.getIntegrationClient();
        return client.ListSourceObjects(companyIntegrationID);
    }
    /** Full automatic "Apply All" flow: pipeline + entity maps + field maps + sync */
    async ApplyAll(companyIntegrationID, sourceObjectNames) {
        const client = this.getIntegrationClient();
        return client.ApplyAll(companyIntegrationID, sourceObjectNames);
    }
    /** Start a sync for a company integration */
    async StartSync(companyIntegrationID) {
        const client = this.getIntegrationClient();
        return client.StartSync(companyIntegrationID);
    }
    /** Get the connector's default configuration for quick setup */
    async GetDefaultConfig(companyIntegrationID) {
        const client = this.getIntegrationClient();
        return client.GetDefaultConfig(companyIntegrationID);
    }
    /** Test connection to the external system */
    async TestConnection(companyIntegrationID) {
        const client = this.getIntegrationClient();
        return client.TestConnection(companyIntegrationID);
    }
    /** Preview source data from the external system via connector's FetchChanges */
    async PreviewSourceData(companyIntegrationID, objectName, limit = 5) {
        const client = this.getIntegrationClient();
        const result = await client.PreviewData(companyIntegrationID, objectName, limit);
        if (!result.Success || !result.Records) {
            console.warn('[IntegrationDataService] PreviewSourceData failed:', result.Message);
            return [];
        }
        return result.Records.map(r => {
            try {
                return JSON.parse(r.Data);
            }
            catch {
                return { _raw: r.Data };
            }
        });
    }
    /** Preview destination data by loading a few records from the target entity */
    async PreviewDestinationData(entityID, limit = 5, provider) {
        const md = this.Provider;
        const entityInfo = md.Entities.find(e => UUIDsEqual(e.ID, entityID));
        if (!entityInfo)
            return [];
        // Load first N fields (skip internal __mj fields) to keep preview compact
        const fields = entityInfo.Fields
            .filter(f => !f.Name.startsWith('__mj'))
            .slice(0, 8)
            .map(f => f.Name);
        const rv = this.createRunView(provider);
        const result = await rv.RunView({
            EntityName: entityInfo.Name,
            ExtraFilter: '',
            OrderBy: `${fields[0]} ASC`,
            MaxRows: limit,
            Fields: fields,
            ResultType: 'simple'
        });
        return result.Results;
    }
    /** Get a count of records in the destination MJ entity */
    async GetDestinationRecordCount(entityID, provider) {
        const md = this.Provider;
        const entityInfo = md.Entities.find(e => UUIDsEqual(e.ID, entityID));
        if (!entityInfo)
            return 0;
        const rv = this.createRunView(provider);
        const result = await rv.RunView({
            EntityName: entityInfo.Name,
            ExtraFilter: '',
            Fields: [entityInfo.FirstPrimaryKey?.Name ?? 'ID'],
            ResultType: 'simple'
        });
        return result.TotalRowCount ?? result.Results.length;
    }
    /** Get the most recent run that touched a given entity */
    async GetLastSyncForEntity(companyIntegrationID, entityID, provider) {
        const rv = this.createRunView(provider);
        // Get runs for this integration, most recent first
        const runsResult = await rv.RunView({
            EntityName: 'MJ: Company Integration Runs',
            ExtraFilter: `CompanyIntegrationID='${companyIntegrationID}'`,
            OrderBy: 'StartedAt DESC',
            MaxRows: 5,
            Fields: ['ID', 'CompanyIntegrationID', 'StartedAt', 'EndedAt', 'TotalRecords', 'Status'],
            ResultType: 'simple'
        });
        if (runsResult.Results.length === 0)
            return null;
        // Check each run's details to find one that touched this entity
        for (const run of runsResult.Results) {
            const detailResult = await rv.RunView({
                EntityName: 'MJ: Company Integration Run Details',
                ExtraFilter: `CompanyIntegrationRunID='${run.ID}' AND EntityID='${entityID}'`,
                MaxRows: 1,
                Fields: ['EntityID'],
                ResultType: 'simple'
            });
            if (detailResult.Results.length > 0) {
                return {
                    StartedAt: run.StartedAt,
                    EndedAt: run.EndedAt,
                    Status: run.Status,
                    TotalRecords: run.TotalRecords
                };
            }
        }
        // No run touched this entity — return the most recent run anyway as context
        const latest = runsResult.Results[0];
        return {
            StartedAt: latest.StartedAt,
            EndedAt: latest.EndedAt,
            Status: latest.Status,
            TotalRecords: latest.TotalRecords
        };
    }
    /** Run a directional sync directly via the GraphQL StartSync mutation. */
    async StartSyncWithDirection(companyIntegrationID, fullSync, syncDirection) {
        const provider = this.Provider;
        const client = new GraphQLIntegrationClient(provider);
        return client.StartSync(companyIntegrationID, undefined, fullSync, syncDirection);
    }
    /** Run an integration sync via the "Run Integration Sync" action */
    async RunSync(companyIntegrationID, fullSync = false) {
        const actionID = await this.lookupActionID('Run Integration Sync');
        if (!actionID) {
            return {
                Success: false,
                Message: 'Action "Run Integration Sync" not found. Ensure the action is registered.'
            };
        }
        const provider = this.Provider;
        const actionClient = new GraphQLActionClient(provider);
        const params = [
            { Name: 'CompanyIntegrationID', Value: companyIntegrationID, Type: 'Input' },
            { Name: 'FullSync', Value: fullSync ? 'true' : 'false', Type: 'Input' },
        ];
        const result = await actionClient.RunAction(actionID, params);
        return { Success: result.Success, Message: result.Message ?? '' };
    }
    /** Load schedule data for all company integrations (includes new scheduling fields) */
    async LoadSchedules(_provider) {
        await IntegrationEngineBase.Instance.Config(false);
        return IntegrationEngineBase.Instance.CompanyIntegrations;
    }
    /** Load sync watermarks for a specific company integration's entity maps */
    async LoadWatermarks(companyIntegrationID, _provider) {
        await IntegrationEngineBase.Instance.Config(false);
        const entityMaps = IntegrationEngineBase.Instance.GetEntityMapsForCompanyIntegration(companyIntegrationID);
        if (entityMaps.length === 0)
            return [];
        const mapIDSet = new Set(entityMaps.map(em => em.ID.toLowerCase()));
        return IntegrationEngineBase.Instance.Watermarks.filter(w => mapIDSet.has(w.EntityMapID.toLowerCase()));
    }
    /**
     * Auto-map all entities in a given schema to a company integration.
     * For each entity in the schema that doesn't already have an entity map,
     * creates an entity map (ExternalObjectName = BaseTable) and 1:1 field maps
     * for every non-system column. Returns the count of maps created.
     */
    async AutoMapSchema(companyIntegrationID, schemaName, direction = 'Pull') {
        const md = this.Provider;
        const errors = [];
        // Get entities in the target schema
        const schemaEntities = md.Entities.filter(e => e.SchemaName.toLowerCase() === schemaName.toLowerCase());
        if (schemaEntities.length === 0) {
            return { EntityMapsCreated: 0, FieldMapsCreated: 0, Errors: [`No entities found in schema "${schemaName}"`] };
        }
        // Load existing entity maps to avoid duplicates
        const existingMaps = await this.LoadEntityMaps(companyIntegrationID);
        const existingEntityIDs = new Set(existingMaps.map(m => m.ID.toLowerCase()));
        // Build a set of entity IDs that already have maps (by EntityID)
        const mappedEntityIDs = new Set();
        for (const em of existingMaps) {
            mappedEntityIDs.add(em.EntityID?.toLowerCase() ?? '');
        }
        // Also load full map data to check EntityID
        const engine = IntegrationEngineBase.Instance;
        await engine.Config(false);
        const allMaps = engine.GetEntityMapsForCompanyIntegration(companyIntegrationID);
        for (const em of allMaps) {
            mappedEntityIDs.add(em.EntityID.toLowerCase());
        }
        let entityMapsCreated = 0;
        let fieldMapsCreated = 0;
        for (const entity of schemaEntities) {
            // Skip if already mapped
            if (mappedEntityIDs.has(entity.ID.toLowerCase()))
                continue;
            // Create entity map
            const entityMap = await this.CreateEntityMap({
                CompanyIntegrationID: companyIntegrationID,
                ExternalObjectName: entity.BaseTable,
                EntityID: entity.ID,
                SyncDirection: direction
            });
            if (!entityMap) {
                errors.push(`Failed to create entity map for ${entity.Name}`);
                continue;
            }
            entityMapsCreated++;
            // Create 1:1 field maps for all non-system fields
            const fields = entity.Fields.filter(f => !f.Name.startsWith('__mj'));
            for (const field of fields) {
                const isKey = field.Name.toLowerCase() === 'uuid' || field.IsPrimaryKey;
                const fm = await this.CreateFieldMap({
                    EntityMapID: entityMap.ID,
                    SourceFieldName: field.Name,
                    DestinationFieldName: field.Name,
                    IsKeyField: isKey,
                    IsRequired: isKey,
                    Direction: direction === 'Bidirectional' ? 'Both' : 'SourceToDest'
                });
                if (fm) {
                    fieldMapsCreated++;
                }
                else {
                    errors.push(`Failed to create field map for ${entity.Name}.${field.Name}`);
                }
            }
        }
        return { EntityMapsCreated: entityMapsCreated, FieldMapsCreated: fieldMapsCreated, Errors: errors };
    }
    /** Load available schemas (for auto-map schema picker) */
    LoadSchemas() {
        const md = this.Provider;
        const schemas = new Set();
        for (const entity of md.Entities) {
            if (entity.SchemaName)
                schemas.add(entity.SchemaName);
        }
        return Array.from(schemas).sort();
    }
    /** Load entity map count per company integration (used by Overview cards) */
    async LoadEntityMapCounts(_provider) {
        await IntegrationEngineBase.Instance.Config(false);
        const counts = new Map();
        for (const em of IntegrationEngineBase.Instance.EntityMaps) {
            if (em.SyncEnabled) {
                const key = em.CompanyIntegrationID.toLowerCase();
                counts.set(key, (counts.get(key) ?? 0) + 1);
            }
        }
        return counts;
    }
    async lookupActionID(actionName) {
        const rv = RunView.FromMetadataProvider(this.Provider);
        const result = await rv.RunView({
            EntityName: 'MJ: Actions',
            ExtraFilter: `Name='${actionName}'`,
            Fields: ['ID'],
            MaxRows: 1,
            ResultType: 'simple'
        });
        return result.Results.length > 0 ? result.Results[0].ID : null;
    }
    getIntegrationClient() {
        const provider = this.Provider;
        return new GraphQLIntegrationClient(provider);
    }
    createRunView(provider) {
        return new RunView((provider ?? this.Provider));
    }
    buildSummary(integration, allRuns, sourceTypes, integrationDefs) {
        const integrationRuns = allRuns.filter(r => UUIDsEqual(r.CompanyIntegrationID, integration.ID));
        const latestRun = integrationRuns.length > 0 ? integrationRuns[0] : null;
        const recentRuns = integrationRuns.slice(0, 5);
        const statusColor = this.computeStatusColor(latestRun, integration.IsActive);
        const relativeTime = this.computeRelativeTime(latestRun?.StartedAt ?? null);
        const totalRecordsSyncedToday = this.computeRecordsSyncedToday(integrationRuns);
        const totalErrors = integrationRuns.filter(r => r.Status === 'Failed').length;
        const durationMs = this.computeDuration(latestRun);
        const sourceType = this.resolveSourceType(integration, sourceTypes);
        const integrationName = integration.Integration ?? '';
        const def = integrationDefs.find(d => d.Name === integrationName);
        const icon = def?.Get('Icon') ?? null;
        return {
            Integration: integration,
            SourceType: sourceType,
            Icon: icon,
            LatestRun: latestRun,
            RecentRuns: recentRuns,
            StatusColor: statusColor,
            RelativeTime: relativeTime,
            TotalRecordsSyncedToday: totalRecordsSyncedToday,
            TotalErrors: totalErrors,
            DurationMs: durationMs
        };
    }
    resolveSourceType(integration, sourceTypes) {
        if (!integration.SourceTypeID)
            return null;
        return sourceTypes.find(st => UUIDsEqual(st.ID, integration.SourceTypeID)) ?? null;
    }
    buildActivityFeedItem(run) {
        return {
            RunID: run.ID,
            IntegrationName: run.Integration,
            Status: run.Status,
            StatusColor: this.runStatusColor(run),
            StartedAt: run.StartedAt,
            RelativeTime: this.computeRelativeTime(run.StartedAt),
            TotalRecords: run.TotalRecords,
            RunByUser: run.RunByUser
        };
    }
    runStatusColor(run) {
        if (run.Status === 'Failed')
            return 'red';
        if (run.Status === 'Success')
            return 'green';
        return 'amber';
    }
    computeRecordsSyncedToday(runs) {
        const todayStart = new Date();
        todayStart.setHours(0, 0, 0, 0);
        return runs
            .filter(r => r.StartedAt && new Date(r.StartedAt) >= todayStart)
            .reduce((acc, r) => acc + r.TotalRecords, 0);
    }
    computeDuration(run) {
        if (!run?.StartedAt || !run?.EndedAt)
            return null;
        return new Date(run.EndedAt).getTime() - new Date(run.StartedAt).getTime();
    }
    aggregateDailyCounts(runs, days) {
        const result = [];
        const today = new Date();
        for (let i = days - 1; i >= 0; i--) {
            const date = new Date(today);
            date.setDate(date.getDate() - i);
            const dateStr = date.toISOString().split('T')[0];
            const label = date.toLocaleDateString(undefined, { weekday: 'short' });
            const dayRecords = runs
                .filter(r => r.StartedAt && r.StartedAt.startsWith(dateStr))
                .reduce((acc, r) => acc + r.TotalRecords, 0);
            result.push({ Date: dateStr, Label: label, Records: dayRecords });
        }
        return result;
    }
    computeStatusColor(latestRun, isActive) {
        if (!isActive)
            return 'gray';
        if (!latestRun)
            return 'gray';
        if (latestRun.Status === 'Failed')
            return 'red';
        if (latestRun.Status === 'In Progress' || latestRun.Status === 'Pending')
            return 'amber';
        if (latestRun.Status === 'Success') {
            return this.isStale(latestRun.StartedAt) ? 'amber' : 'green';
        }
        return 'gray';
    }
    isStale(startedAt) {
        if (!startedAt)
            return true;
        const runDate = new Date(startedAt);
        const hoursAgo = (Date.now() - runDate.getTime()) / (1000 * 60 * 60);
        return hoursAgo > 24;
    }
    computeRelativeTime(dateStr) {
        if (!dateStr)
            return 'Never run';
        const date = new Date(dateStr);
        const diffMs = Date.now() - date.getTime();
        const diffMinutes = Math.floor(diffMs / (1000 * 60));
        if (diffMinutes < 1)
            return 'Just now';
        if (diffMinutes < 60)
            return `${diffMinutes}m ago`;
        const diffHours = Math.floor(diffMinutes / 60);
        if (diffHours < 24)
            return `${diffHours}h ago`;
        const diffDays = Math.floor(diffHours / 24);
        if (diffDays < 30)
            return `${diffDays}d ago`;
        return date.toLocaleDateString();
    }
    static ɵfac = function IntegrationDataService_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || IntegrationDataService)(); };
    static ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: IntegrationDataService, factory: IntegrationDataService.ɵfac, providedIn: 'root' });
}
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(IntegrationDataService, [{
        type: Injectable,
        args: [{
                providedIn: 'root'
            }]
    }], null, null); })();
//# sourceMappingURL=integration-data.service.js.map