import {Projection as VgProjection, SignalRef} from 'vega';
import {contains} from '../../util.js';
import {isSignalRef} from '../../vega.schema.js';
import {isConcatModel, isLayerModel, Model} from '../model.js';

export function assembleProjections(model: Model): VgProjection[] {
  if (isLayerModel(model) || isConcatModel(model)) {
    return assembleProjectionsForModelAndChildren(model);
  } else {
    return assembleProjectionForModel(model);
  }
}

export function assembleProjectionsForModelAndChildren(model: Model): VgProjection[] {
  return model.children.reduce((projections, child) => {
    return projections.concat(child.assembleProjections());
  }, assembleProjectionForModel(model));
}

export function assembleProjectionForModel(model: Model): VgProjection[] {
  const component = model.component.projection;
  if (!component || component.merged) {
    return [];
  }

  const projection = component.combine();
  const {name} = projection; // we need to extract name so that it is always present in the output and pass TS type validation

  if (!component.data) {
    // generate custom projection, no automatic fitting
    return [
      {
        name,
        // translate to center by default
        translate: {signal: '[width / 2, height / 2]'},
        // parameters, overwrite default translate if specified
        ...projection,
      },
    ];
  } else {
    // generate projection that uses extent fitting
    const size: SignalRef = {
      signal: `[${component.size.map((ref) => ref.signal).join(', ')}]`,
    };

    const fits: string[] = component.data.reduce((sources, data) => {
      const source: string = isSignalRef(data) ? data.signal : `data('${model.lookupDataSource(data)}')`;
      if (!contains(sources, source)) {
        // build a unique list of sources
        sources.push(source);
      }
      return sources;
    }, []);

    if (fits.length <= 0) {
      throw new Error("Projection's fit didn't find any data sources");
    }

    return [
      {
        name,
        size,
        fit: {
          signal: fits.length > 1 ? `[${fits.join(', ')}]` : fits[0],
        },
        ...projection,
      },
    ];
  }
}
