# -*- coding: utf-8 -*-
"""Linear Directional Mean Processing Algorithm."""
from __future__ import annotations

import logging
import math
import numpy as np

from qgis.PyQt.QtCore import QVariant
from ._mixins import HelpUrlMixin
from qgis.core import (
    QgsFeature,
    QgsField,
    QgsFields,
    QgsPointXY,
    QgsGeometry,
    QgsProject,
    QgsWkbTypes,
    QgsProcessing,
    QgsProcessingAlgorithm,
    QgsProcessingException,
    QgsProcessingParameterFeatureSource,
    QgsProcessingParameterFeatureSink
)

from ..core.stats_engines import calculate_linear_directional_mean
from ..core.layer_metadata import apply_output_metadata

from ._icons import algorithm_icon


logger = logging.getLogger("PlanX GeoStats Lab")


class LinearDirectionalMeanAlgorithm(HelpUrlMixin, QgsProcessingAlgorithm):
    INPUT = "INPUT"
    OUTPUT = "OUTPUT"

    def __init__(self):
        super().__init__()
        self.out_layer_id = None

    def name(self) -> str:
        return "linear_directional_mean"

    def displayName(self) -> str:
        return "Linear Directional Mean"

    def group(self) -> str:
        return "04 | Centers, Direction and Dispersion"

    def groupId(self) -> str:
        return "planx_center_direction_spread"

    def icon(self):
        return algorithm_icon("linear_directional_mean")

    def createInstance(self):
        return LinearDirectionalMeanAlgorithm()

    def shortHelpString(self) -> str:
        return (
            "Computes the length-weighted circular mean direction and mean "
            "length of a set of line features, using proper circular "
            "(periodic) statistics so that a line at 359 degrees and one at 1 "
            "degree correctly average to 0 degrees, not 180. Each line's "
            "Euclidean start-to-end length is its implicit weight in the "
            "circular mean - no separate weight field is needed or accepted. "
            "For multi-part geometries, only the first component is used.\n\n"
            "Output fields: mean_angle (compass bearing, 0 = North, "
            "clockwise), mean_length, center_x/center_y (the geographic mean "
            "of all line midpoints), and line_count. Internally the tool also "
            "computes the mean resultant length Rbar (0 to 1, how strongly "
            "lines agree on a direction), which is not exposed as an output "
            "field but drives the trend line's visual length; Rbar near 1 "
            "means strong directional consensus, near 0 means lines point in "
            "all directions with no preference - most real street networks "
            "fall in the 0.1-0.5 range, reflecting grid dominance without one "
            "single preferred bearing.\n\n"
            "This computes a directional mean, not an axial mean: a line at "
            "45 degrees and one at 225 degrees point in opposite directions "
            "and are treated as different bearings, even though they lie on "
            "the same street. For undirected orientation analysis (where a "
            "north-south street should be treated the same regardless of "
            "which end is 'start'), double every angle before computing the "
            "mean and halve the result afterward - this tool does not do that "
            "transformation automatically."
        )

    def initAlgorithm(self, config=None):
        self.addParameter(
            QgsProcessingParameterFeatureSource(
                self.INPUT,
                "Input line vector layer",
                [QgsProcessing.TypeVectorLine]
            )
        )
        self.addParameter(
            QgsProcessingParameterFeatureSink(
                self.OUTPUT,
                "Output directional mean line"
            )
        )

    def processAlgorithm(self, parameters, context, feedback):
        source = self.parameterAsSource(parameters, self.INPUT, context)
        if source is None:
            raise QgsProcessingException("Invalid input layer source.")

        # Extract line start/end coordinates
        s_x, s_y, e_x, e_y = [], [], [], []

        feedback.pushInfo("Extracting line endpoints...")
        total = source.featureCount() or 1
        for idx, f in enumerate(source.getFeatures()):
            if feedback.isCanceled():
                break

            geom = f.geometry()
            if geom is None or geom.isEmpty() or geom.type() != QgsWkbTypes.LineGeometry:
                continue

            # Get the first polyline part
            if geom.isMultipart():
                parts = geom.asMultiPolyline()
                if not parts or not parts[0]:
                    continue
                line = parts[0]
            else:
                line = geom.asPolyline()
                if not line:
                    continue

            start_pt = line[0]
            end_pt = line[-1]

            s_x.append(start_pt.x())
            s_y.append(start_pt.y())
            e_x.append(end_pt.x())
            e_y.append(end_pt.y())

            feedback.setProgress(int(50 * (idx / total)))

        n_lines = len(s_x)
        if n_lines == 0:
            raise QgsProcessingException("No valid line features found in the input layer.")

        feedback.pushInfo(f"Processing {n_lines} line features...")

        center_x, center_y, mean_angle, mean_length = calculate_linear_directional_mean(
            np.array(s_x), np.array(s_y),
            np.array(e_x), np.array(e_y)
        )

        feedback.pushInfo(f"Mean Angle: {mean_angle:.2f} degrees  |  Mean Length: {mean_length:.4f}  |  Center: ({center_x:.4f}, {center_y:.4f})")

        # Build the output trend line
        angle_rad = math.radians(mean_angle)
        half_len = mean_length / 2.0

        # Compass bearing: dx = sin(angle), dy = cos(angle)
        dx = half_len * math.sin(angle_rad)
        dy = half_len * math.cos(angle_rad)

        pt_start = QgsPointXY(center_x - dx, center_y - dy)
        pt_end = QgsPointXY(center_x + dx, center_y + dy)

        out_fields = QgsFields()
        out_fields.append(QgsField("mean_angle", QVariant.Double, len=10, prec=4))
        out_fields.append(QgsField("mean_length", QVariant.Double, len=15, prec=6))
        out_fields.append(QgsField("center_x", QVariant.Double, len=15, prec=6))
        out_fields.append(QgsField("center_y", QVariant.Double, len=15, prec=6))
        out_fields.append(QgsField("line_count", QVariant.Int))

        (sink, dest_id) = self.parameterAsSink(
            parameters,
            self.OUTPUT,
            context,
            out_fields,
            QgsWkbTypes.LineString,
            source.sourceCrs()
        )
        self.out_layer_id = dest_id

        out_feat = QgsFeature()
        out_feat.setGeometry(QgsGeometry.fromPolylineXY([pt_start, pt_end]))
        out_feat.setFields(out_fields)
        out_feat.setAttribute("mean_angle", mean_angle)
        out_feat.setAttribute("mean_length", mean_length)
        out_feat.setAttribute("center_x", center_x)
        out_feat.setAttribute("center_y", center_y)
        out_feat.setAttribute("line_count", n_lines)

        sink.addFeature(out_feat)
        feedback.setProgress(100)

        return {self.OUTPUT: dest_id}

    def postProcessAlgorithm(self, context, feedback):
        if self.out_layer_id is None:
            return {}
        layer = QgsProject.instance().mapLayer(self.out_layer_id)
        if not layer:
            return {}
        apply_output_metadata(
            layer,
            "PlanX GeoStats linear directional mean output",
            {
                "mean_angle": "Mean line direction angle in degrees",
                "mean_length": "Mean input line length used for the trend line",
                "center_x": "Trend line center X coordinate",
                "center_y": "Trend line center Y coordinate",
                "line_count": "Number of valid line features used",
            },
            self.displayName(),
        )
        return {}
