import { clamp, isEqual } from "lodash";
import { CompositeLayer } from "@deck.gl/core";
import { PathLayer } from "@deck.gl/layers";
import { getPositionByMD } from "../utils/wells";
import { getCurveValueAtMd, getCurveValueAtRow, getLegendData, getLogColor, getLogMd, getLogSegmentIndexForMd, getLogWidth, injectMdRows, } from "../utils/log";
import { getMdsInRange } from "../utils/trajectory";
import { getFromAccessor } from "../../utils/layerTools";
export var SubLayerId;
(function (SubLayerId) {
    SubLayerId["CURVE"] = "curve";
    SubLayerId["SELECTION"] = "selection";
})(SubLayerId || (SubLayerId = {}));
/** Aggregation layer for displaying Log Curves a long a well path */
export class LogCurveLayer extends CompositeLayer {
    constructor(...args) {
        super(...args);
        this._getFromSourceAccessor = this._getFromSourceAccessor.bind(this);
        this._logCurveIsVisible = this._logCurveIsVisible.bind(this);
        this.getCurveLayer = this.getCurveLayer.bind(this);
        this.getSelectionLayer = this.getSelectionLayer.bind(this);
    }
    updateState({ props, oldProps, changeFlags, }) {
        var _a, _b;
        if (!changeFlags.propsOrDataChanged)
            return;
        if (!this.isLoaded)
            return;
        if (!props.data)
            return;
        if (!isEqual(props.domainSelection, oldProps.domainSelection)) {
            this.setState({ domainSelection: props.domainSelection });
        }
        if (props.data === oldProps.data &&
            props.trajectoryData === oldProps.trajectoryData)
            return;
        // Deck.gl ensures data is a loaded array at this point
        const layerData = props.data;
        const subLayerData = [];
        // Iterate over data to create subLayer data
        layerData.forEach((d) => {
            const wellFeatureIndex = props.trajectoryData.features.findIndex((f) => f.properties.name === d.header.well);
            const wellFeatureObject = props.trajectoryData.features[wellFeatureIndex];
            const extendedLog = injectMdRows(d, ...wellFeatureObject.properties.md[0]);
            subLayerData.push(
            // this.getSubLayerRow(d, wellFeatureObject, wellFeatureIndex)
            this.getSubLayerRow(extendedLog, wellFeatureObject, wellFeatureIndex));
        });
        (_b = (_a = this.props).onLogDataComputed) === null || _b === void 0 ? void 0 : _b.call(_a, subLayerData);
        this.setState({ subLayerData });
    }
    setLegend(value) {
        this.setState({
            legend: this.getLegendData(value),
        });
    }
    getLegendData(value) {
        return getLegendData(value, "", this.props.activeLog.logRun, this.props.colorScale.name);
    }
    getCurveLayer() {
        return this.getSubLayers().find((l) => l.id === this.getSubLayerProps({ id: SubLayerId.CURVE }).id);
    }
    getSelectionLayer() {
        return this.getSubLayers().find((l) => l.id === this.getSubLayerProps({ id: SubLayerId.SELECTION }).id);
    }
    // By default, the picking info would be the well feature, since we used getSubLayerRow
    // So we override it to return the log data instead
    getPickingInfo({ info }) {
        return info;
    }
    _getFromSourceAccessor(accessor, d, ctx) {
        // ! deck.gl does not transform the typing to the correct `In` type
        const sourceAccessor = this.getSubLayerAccessor(accessor);
        return getFromAccessor(sourceAccessor, d, ctx);
    }
    _logCurveIsVisible(d, ctx) {
        const { activeLog, getTrajectoryPath } = this.props;
        if (!activeLog)
            return false;
        const sourcePath = this._getFromSourceAccessor(getTrajectoryPath, d, ctx);
        return Boolean(sourcePath === null || sourcePath === void 0 ? void 0 : sourcePath.length);
    }
    renderLayers() {
        var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
        const { getTrajectoryPath, getWellMds } = this.props;
        const sharedProps = {
            data: this.state.subLayerData,
            widthScale: 10,
            widthMinPixels: 1,
            miterLimit: 1000,
        };
        return [
            new PathLayer(Object.assign(Object.assign({}, this.getSubLayerProps(Object.assign(Object.assign({}, sharedProps), { id: SubLayerId.CURVE, pickable: true, updateTriggers: {
                    getPath: [
                        (_a = this.props.activeLog) === null || _a === void 0 ? void 0 : _a.logRun,
                        this.props.trajectoryData,
                        this.props.positionFormat,
                    ],
                    getColor: [
                        (_b = this.props.activeLog) === null || _b === void 0 ? void 0 : _b.logRun,
                        (_c = this.props.activeLog) === null || _c === void 0 ? void 0 : _c.curveName,
                        this.props.colorScale.name,
                        this.props.colorScale.colorTables,
                        this.props.colorScale.isLogarithmic,
                        this.props.colorScale.mappingFunction,
                    ],
                    getWidth: [
                        (_d = this.props.activeLog) === null || _d === void 0 ? void 0 : _d.logRun,
                        (_e = this.props.activeLog) === null || _e === void 0 ? void 0 : _e.curveName,
                        this.props.logWidth,
                    ],
                } }))), { 
                // The log curve layer is (usually) used alongside the trajectory curve layer. We want the log line to appear right *behind* the trajectory, from all angles. A positive layer-index will push it away from the camera
                getPolygonOffset: ({ layerIndex }) => [2, layerIndex * 1000], getPath: (d, ctx) => {
                    var _a;
                    if (!this._logCurveIsVisible(d, ctx))
                        return [];
                    return getLogPath(d, (_a = this.props.activeLog) === null || _a === void 0 ? void 0 : _a.logRun, this._getFromSourceAccessor(getTrajectoryPath, d, ctx), this._getFromSourceAccessor(getWellMds, d, ctx));
                }, getColor: (d, ctx) => {
                    var _a, _b;
                    if (!this._logCurveIsVisible(d, ctx))
                        return [];
                    return getLogColor(d, (_a = this.props.activeLog) === null || _a === void 0 ? void 0 : _a.logRun, (_b = this.props.activeLog) === null || _b === void 0 ? void 0 : _b.curveName, this.props.colorScale.name, 
                    // This is from the context, should it just be inferred from the lifecycle?
                    this.props.colorScale.colorTables, this.props.colorScale.mappingFunction, this.props.colorScale.isLogarithmic);
                }, getWidth: this.props.logWidth !== "value"
                    ? this.props.logWidth
                    : (d, ctx) => {
                        var _a, _b;
                        if (!this._logCurveIsVisible(d, ctx))
                            return [];
                        return getLogWidth(d, (_a = this.props.activeLog) === null || _a === void 0 ? void 0 : _a.logRun, (_b = this.props.activeLog) === null || _b === void 0 ? void 0 : _b.curveName);
                    } })),
            new PathLayer(Object.assign(Object.assign({}, this.getSubLayerProps(Object.assign(Object.assign({}, sharedProps), { id: SubLayerId.SELECTION, widthScale: sharedProps.widthScale * 1.5, updateTriggers: {
                    getWidth: [
                        this.props.logWidth,
                        (_f = this.props.activeLog) === null || _f === void 0 ? void 0 : _f.logRun,
                        (_g = this.props.activeLog) === null || _g === void 0 ? void 0 : _g.curveName,
                        (_h = this.state.domainSelection) === null || _h === void 0 ? void 0 : _h.well,
                        (_j = this.state.domainSelection) === null || _j === void 0 ? void 0 : _j.selection,
                    ],
                    getColor: [
                        (_k = this.props.activeLog) === null || _k === void 0 ? void 0 : _k.logRun,
                        (_l = this.state.domainSelection) === null || _l === void 0 ? void 0 : _l.well,
                        (_m = this.state.domainSelection) === null || _m === void 0 ? void 0 : _m.selection,
                    ],
                    getPath: [
                        (_o = this.props.activeLog) === null || _o === void 0 ? void 0 : _o.logRun,
                        (_p = this.state.domainSelection) === null || _p === void 0 ? void 0 : _p.well,
                        (_q = this.state.domainSelection) === null || _q === void 0 ? void 0 : _q.selection,
                    ],
                } }))), { 
                // Similar to the curve layer, we want the selection to be rendered *over* the curve and trajectory layer. A negative offset pulls it towards the camera
                getPolygonOffset: ({ layerIndex }) => [2, layerIndex * -1000], getPath: (d, ctx) => {
                    var _a, _b, _c;
                    if (!this._logCurveIsVisible(d, ctx))
                        return [];
                    return getSelectionPath(d, this._getFromSourceAccessor(getTrajectoryPath, d, ctx), this._getFromSourceAccessor(getWellMds, d, ctx), (_a = this.props.activeLog) === null || _a === void 0 ? void 0 : _a.logRun, (_b = this.state.domainSelection) === null || _b === void 0 ? void 0 : _b.well, (_c = this.state.domainSelection) === null || _c === void 0 ? void 0 : _c.selection);
                }, getColor: (d, ctx) => {
                    var _a, _b, _c;
                    if (!this._logCurveIsVisible(d, ctx))
                        return [];
                    return getSelectionColor(d, (_a = this.props.activeLog) === null || _a === void 0 ? void 0 : _a.logRun, (_b = this.state.domainSelection) === null || _b === void 0 ? void 0 : _b.well, (_c = this.state.domainSelection) === null || _c === void 0 ? void 0 : _c.selection);
                }, getWidth: this.props.logWidth !== "value"
                    ? this.props.logWidth
                    : (d, ctx) => {
                        var _a, _b, _c, _d;
                        if (!this._logCurveIsVisible(d, ctx))
                            return [];
                        return getSelectionWidth(d, (_a = this.props.activeLog) === null || _a === void 0 ? void 0 : _a.logRun, (_b = this.props.activeLog) === null || _b === void 0 ? void 0 : _b.curveName, (_c = this.state.domainSelection) === null || _c === void 0 ? void 0 : _c.well, (_d = this.state.domainSelection) === null || _d === void 0 ? void 0 : _d.selection);
                    } })),
        ];
    }
}
LogCurveLayer.layerName = "Log Curve Layer";
function getLogPath(d, selectedLogRun, wellTrajectory, wellMds) {
    if (!selectedLogRun)
        return [];
    if (!(wellTrajectory === null || wellTrajectory === void 0 ? void 0 : wellTrajectory.length) || !(wellMds === null || wellMds === void 0 ? void 0 : wellMds.length))
        return [];
    const log_xyz = [];
    const log_mds = getLogMd(d, selectedLogRun);
    log_mds.forEach((md) => {
        const xyz = getPositionByMD(wellTrajectory, wellMds, md);
        log_xyz.push(xyz);
    });
    return log_xyz;
}
function getSelectionMds(d, logRunName, selectedWell, selectedRange) {
    if (!logRunName || !selectedWell || !selectedRange)
        return [];
    if (selectedWell !== d.header.well)
        return [];
    const logMds = getLogMd(d, logRunName);
    const sanitizedRange = sanitizeSelectionRange(logMds, selectedRange);
    if (!sanitizedRange.length)
        return [];
    let [mdStart, mdEnd] = sanitizedRange;
    // Add caps; i.e. colored lines at start and end
    const capWidth = mdEnd === undefined ? 2 : 4;
    if (mdStart - capWidth <= logMds[0]) {
        mdStart = logMds[0] + capWidth;
    }
    if (mdStart + capWidth >= logMds.at(-1)) {
        mdStart = logMds.at(-1) - capWidth;
    }
    if (mdEnd === undefined) {
        return [mdStart - capWidth, mdStart + capWidth];
    }
    else {
        if (mdEnd + capWidth >= logMds.at(-1)) {
            mdEnd = logMds.at(-1) - capWidth;
        }
        if (mdEnd < mdStart) {
            mdEnd = mdStart;
        }
        return [
            mdStart - capWidth,
            ...getMdsInRange(logMds, mdStart, mdEnd),
            mdEnd + capWidth,
        ];
    }
}
function getSelectionPath(d, wellTrajectory, wellMds, logRunName, selectedWell, selectedRange) {
    if (!(wellTrajectory === null || wellTrajectory === void 0 ? void 0 : wellTrajectory.length) || !wellMds.length)
        return [];
    return getSelectionMds(d, logRunName, selectedWell, selectedRange).map((md) => getPositionByMD(wellTrajectory, wellMds, md));
}
function getSelectionColor(d, logRunName, selectedWell, selectedRange) {
    const [rangeStart, rangeEnd] = selectedRange !== null && selectedRange !== void 0 ? selectedRange : [];
    const swapped = rangeStart !== undefined &&
        rangeStart !== undefined &&
        rangeStart > rangeEnd;
    const startColor = swapped ? [0, 255, 0, 128] : [255, 0, 0, 128];
    const endColor = swapped ? [255, 0, 0, 128] : [0, 255, 0, 128];
    const selectionMds = getSelectionMds(d, logRunName, selectedWell, selectedRange);
    return selectionMds.map((_, idx) => {
        if (idx === 0)
            return startColor;
        if (idx === selectionMds.length - 2)
            return endColor;
        return [128, 128, 128, 128];
    });
}
const SELECTION_MIN_WIDTH = 1;
function getSelectionWidth(d, logRunName, logCurveName, selectedWell, selectedRange) {
    if (!logRunName || !logCurveName)
        return 0;
    const selectionMds = getSelectionMds(d, logRunName, selectedWell, selectedRange);
    if (!selectionMds.length)
        return [];
    // We'll only need to interpolate the values for the ends of the array, each other point will be in the log
    // The "caps" of the selection is currently a two-point line segment
    const logRowOffset = getLogSegmentIndexForMd(d, selectionMds[1]) - 1;
    return selectionMds.map((md, idx) => {
        var _a;
        let width = 0;
        if (idx > 1 && idx < selectionMds.length - 2) {
            width = getCurveValueAtRow(d, logCurveName, idx + logRowOffset);
        }
        else {
            // Interpolate ends of selection
            width = (_a = getCurveValueAtMd(d, logCurveName, md)) !== null && _a !== void 0 ? _a : 0;
        }
        return Math.max(width, SELECTION_MIN_WIDTH);
    });
}
export function sanitizeSelectionRange(mdArray, range) {
    if (mdArray.length < 2)
        return [];
    if (!range)
        return [];
    let start = range.at(0);
    let end = range.at(-1);
    if (start === undefined)
        return [];
    start = clamp(start, mdArray.at(0), mdArray.at(-1));
    if (end === undefined)
        return [start];
    end = clamp(end, mdArray.at(0), mdArray.at(-1));
    if (start === end)
        return [start];
    if (start > end)
        return [end, start];
    return [start, end];
}
//# sourceMappingURL=logCurveLayer.js.map