export const EMPTY_SCENE_DATA = Object.freeze({
  background: '#111',
  camera: Object.freeze({ position: Object.freeze({ x: 0, y: 5, z: 10 }) }),
  lights: Object.freeze([{ type: 'ambient', intensity: 0.5 }]),
  objects: Object.freeze([]),
});

export function segmentSceneKey(trackId, segmentIdentity) {
  return `${trackId}_${segmentIdentity}`;
}

function sceneInstanceKey(trackId, segmentIndex, segment) {
  return segmentSceneKey(trackId, segment?.id ?? segmentIndex);
}

function cloneJson(value) {
  return JSON.parse(JSON.stringify(value));
}

export function ensureSceneSegmentOverrideOwnership(tracks, {
  readStoredOverride = () => null,
  storagePrefix = 'demotool_scene_',
  storageVersion = 2,
  logger = console,
} = {}) {
  let migrated = 0;
  for (const track of tracks || []) {
    if (track?.type !== 'scene') continue;
    for (const segment of track.segments || []) {
      if (segment.sceneOverrides !== undefined) continue;

      let overrides = {};
      if (segment.sceneFile) {
        const filename = segment.sceneFile.split('/').pop().replace(/\.json$/, '');
        const storageKey = storagePrefix + filename;
        const stored = readStoredOverride(storageKey);
        if (stored) {
          try {
            const parsed = typeof stored === 'string' ? JSON.parse(stored) : stored;
            if (!parsed._version || parsed._version === storageVersion) {
              const storedOverrides = parsed.data || parsed;
              if (storedOverrides && typeof storedOverrides === 'object') {
                overrides = storedOverrides;
              }
            }
          } catch (error) {
            logger.warn?.(`Failed to migrate stored scene overrides for ${filename}:`, error);
          }
        }
      }

      segment.sceneOverrides = cloneJson(overrides);
      migrated += 1;
    }
  }
  return migrated;
}

export class SegmentSceneInstanceStore extends Map {
  constructor({
    loadScene,
    createScene,
    mergeSceneData,
    getLegacyScene = () => null,
    readStoredOverride = () => null,
    removeStoredOverride = () => {},
    disposeInstance = () => {},
    storagePrefix = 'demotool_scene_',
    storageVersion = 2,
    fallbackScene = EMPTY_SCENE_DATA,
    logger = console,
  }) {
    super();
    this.dependencies = {
      loadScene, createScene, mergeSceneData, getLegacyScene,
      readStoredOverride, removeStoredOverride, disposeInstance,
      storagePrefix, storageVersion, fallbackScene, logger,
    };
    this.loading = new Map();
  }

  invalidate(key) {
    this.loading.delete(key);
  }

  async loadData(segment) {
    const deps = this.dependencies;
    let data = null;
    if (segment.sceneFile) {
      try {
        data = await deps.loadScene(segment.sceneFile);
        data._sourceFile = segment.sceneFile;
        if (segment.sceneOverrides !== undefined) {
          if (segment.sceneOverrides && typeof segment.sceneOverrides === 'object') {
            data = deps.mergeSceneData(data, segment.sceneOverrides);
          }
        } else {
          const filename = segment.sceneFile.split('/').pop().replace(/\.json$/, '');
          const storageKey = deps.storagePrefix + filename;
          const stored = deps.readStoredOverride(storageKey);
          if (stored) {
            try {
              const parsed = typeof stored === 'string' ? JSON.parse(stored) : stored;
              if (parsed._version && parsed._version !== deps.storageVersion) {
                deps.removeStoredOverride(storageKey);
              } else {
                const overrides = parsed.data || parsed;
                if (overrides && typeof overrides === 'object') {
                  data = deps.mergeSceneData(data, overrides);
                }
              }
            } catch (error) {
              deps.logger.warn?.(`Failed to load stored scene overrides for ${filename}:`, error);
            }
          }
        }
      } catch (error) {
        deps.logger.warn?.(`Failed to load scene file ${segment.sceneFile}:`, error);
      }
    }

    if (!data && Number.isInteger(segment.sceneIndex) && segment.sceneIndex >= 0) {
      const legacy = deps.getLegacyScene(segment.sceneIndex);
      if (legacy?.data) data = cloneJson(legacy.data);
    }
    return data || cloneJson(deps.fallbackScene);
  }

  async getOrCreate(trackId, segmentIndex, segment) {
    const key = sceneInstanceKey(trackId, segmentIndex, segment);
    if (this.has(key)) return this.get(key);
    if (this.loading.has(key)) return this.loading.get(key);

    let pending;
    pending = (async () => {
      const instance = await this.dependencies.createScene(await this.loadData(segment));
      if (this.loading.get(key) !== pending) {
        this.dependencies.disposeInstance(instance);
        return null;
      }
      instance._segmentKey = key;
      instance._segmentId = segment?.id ?? null;
      instance._trackId = trackId;
      instance._segIndex = segmentIndex;
      this.set(key, instance);
      return instance;
    })();
    this.loading.set(key, pending);
    try {
      return await pending;
    } finally {
      if (this.loading.get(key) === pending) this.loading.delete(key);
    }
  }

  ensure(trackId, segmentIndex, segment) {
    const key = sceneInstanceKey(trackId, segmentIndex, segment);
    if (this.has(key) || this.loading.has(key)) return;
    this.getOrCreate(trackId, segmentIndex, segment).catch(error => {
      this.dependencies.logger.error?.(`Failed to load scene instance for ${key}:`, error);
    });
  }

  release(trackId, segmentIndex, segment = null) {
    const key = sceneInstanceKey(trackId, segmentIndex, segment);
    this.invalidate(key);
    const instance = this.get(key);
    if (!instance) return false;
    this.dependencies.disposeInstance(instance);
    return this.delete(key);
  }

  releaseTrack(trackId) {
    let released = 0;
    const keyPrefix = `${trackId}_`;
    for (const key of [...this.loading.keys()]) {
      if (key.startsWith(keyPrefix)) this.invalidate(key);
    }
    for (const [key, instance] of [...this.entries()]) {
      if (instance._trackId !== trackId) continue;
      this.dependencies.disposeInstance(instance);
      this.delete(key);
      released += 1;
    }
    return released;
  }

  retain(tracks) {
    const retained = new Set();
    for (const track of tracks) {
      for (let index = 0; index < track.segments.length; index += 1) {
        retained.add(sceneInstanceKey(track.id, index, track.segments[index]));
      }
    }
    for (const [key, instance] of [...this.entries()]) {
      if (retained.has(key)) continue;
      this.invalidate(key);
      this.dependencies.disposeInstance(instance);
      this.delete(key);
    }
    for (const key of [...this.loading.keys()]) {
      if (!retained.has(key)) this.invalidate(key);
    }
  }

  dispose() {
    for (const key of [...this.loading.keys()]) this.invalidate(key);
    for (const instance of this.values()) {
      this.dependencies.disposeInstance(instance);
    }
    this.clear();
    this.loading.clear();
  }

  async preload(tracks) {
    for (const track of tracks) {
      for (let index = 0; index < track.segments.length; index += 1) {
        await this.getOrCreate(track.id, index, track.segments[index]);
      }
    }
  }
}

export function createSegmentSceneInstanceStore(dependencies) {
  return new SegmentSceneInstanceStore(dependencies);
}
