import React, { useState } from "react";
import { __, sprintf } from "@wordpress/i18n";
import { Info, Plus, Trash2 } from "lucide-react";
import AjaxSelect from "./AjaxSelect";
import RangeSlider, { rangeColor } from "./RangeSlider";

/**
 * "Discounts and Freebies" screen: basis select, multi-point range slider,
 * and the per-range freebies/discount editors + summary list.
 */
const DiscountBuilder = ({ discounts, onChange, currencySymbol }) => {
	const isCount = discounts.basis === "total_count";
	const boundaries = discounts.boundaries;
	const ranges = discounts.ranges;
	const parsedMaxValue = Number(discounts.max_value);
	const maxValue = Number.isFinite(parsedMaxValue) && parsedMaxValue > 0 ? parsedMaxValue : 0;
	// Draft text for the per-range "From" inputs — committed on blur/Enter so
	// mid-typing intermediate values never cascade and corrupt neighbors.
	const [drafts, setDrafts] = useState({});

	// The maximum only sizes the picker. Keep saved points and offers editable
	// when it is blank, with room beyond the last point for the open-ended range.
	const highestPoint = Math.max(0, ...boundaries);
	const automaticMax = Math.max(isCount ? 10 : 100, highestPoint * 1.25);
	const scaleMax = maxValue || (isCount ? Math.ceil(automaticMax) : Math.ceil(automaticMax * 100) / 100);

	const formatPoint = (value) =>
		isCount
			? String(Math.round(value))
			: `${currencySymbol}${Number(value).toFixed(2).replace(/\.00$/, "")}`;

	const rangeBoundsLabel = (index) => {
		const from = index === 0 ? 0 : boundaries[index - 1];
		const to = index < boundaries.length ? boundaries[index] : null;
		if (to === null) {
			return sprintf(
				/* translators: %s: range start. */
				__("%s and above", "arraysubs"),
				formatPoint(from)
			);
		}
		return `${formatPoint(from)} – ${formatPoint(to)}`;
	};

	const emptyRange = () => ({ freebies: [], discount: { type: "none", amount: 0 } });

	/**
	 * Re-fit boundary points into a [0, nextMax] domain, keeping every point's
	 * rules attached to it. Points past the edge clamp onto it — leaving them
	 * outside would hide them from the slider while they kept discounting the
	 * storefront box — and points that collide after clamping drop with their
	 * range. Mirrors BoxConfig::sanitizeDiscounts() so the picker shows exactly
	 * what will be saved.
	 */
	const fitToDomain = (nextMax, sourceBoundaries, sourceRanges) => {
		const nextBoundaries = [];
		const nextRanges = [sourceRanges[0] || emptyRange()];
		// Source points are already ascending and clamping is monotonic, so the
		// result stays sorted.
		sourceBoundaries.forEach((boundary, index) => {
			const clamped = nextMax > 0 ? Math.min(boundary, nextMax) : boundary;
			if (clamped <= 0 || nextBoundaries.includes(clamped)) {
				return;
			}
			nextBoundaries.push(clamped);
			nextRanges.push(sourceRanges[index + 1] || emptyRange());
		});
		return { boundaries: nextBoundaries, ranges: nextRanges };
	};

	const setBasis = (basis) => {
		// Switching basis invalidates boundary units — round for counts.
		const roundedBoundaries =
			basis === "total_count"
				? boundaries.map((b) => Math.max(1, Math.round(b)))
				: [...boundaries];
		const nextMax =
			basis === "total_count" && maxValue > 0 ? Math.max(1, Math.round(maxValue)) : maxValue;
		// Rounding can push a point past a rounded-down max, so re-fit both.
		const fitted = fitToDomain(nextMax, roundedBoundaries, ranges);
		onChange({
			...discounts,
			basis,
			max_value: nextMax,
			boundaries: fitted.boundaries,
			ranges: fitted.ranges,
		});
	};

	const setMaxValue = (rawValue) => {
		onChange({ ...discounts, max_value: rawValue });
	};

	const normalizeMaxValue = (rawValue) => {
		const value = Number(rawValue);
		if (!Number.isFinite(value) || value <= 0) {
			setMaxValue("");
			return;
		}
		const nextMax = isCount ? Math.max(1, Math.round(value)) : Math.round(value * 100) / 100;
		const fitted = fitToDomain(nextMax, boundaries, ranges);
		onChange({
			...discounts,
			max_value: nextMax,
			boundaries: fitted.boundaries,
			ranges: fitted.ranges,
		});
	};

	const setBoundaries = (nextBoundaries) => {
		onChange({ ...discounts, boundaries: nextBoundaries });
	};

	const addPoint = () => {
		if (boundaries.length >= 10) {
			return;
		}
		// New point: midpoint of the largest gap (including the tail gap).
		const edges = [0, ...boundaries, scaleMax];
		let bestGapIndex = 0;
		let bestGapSize = -1;
		for (let i = 0; i < edges.length - 1; i++) {
			const size = edges[i + 1] - edges[i];
			if (size > bestGapSize) {
				bestGapSize = size;
				bestGapIndex = i;
			}
		}
		let point = (edges[bestGapIndex] + edges[bestGapIndex + 1]) / 2;
		point = isCount ? Math.max(1, Math.round(point)) : Math.round(point * 100) / 100;
		if (boundaries.includes(point)) {
			return;
		}

		const nextBoundaries = [...boundaries, point].sort((a, b) => a - b);
		const insertIndex = nextBoundaries.indexOf(point);
		const nextRanges = [...ranges];
		// The split range keeps its rules; the new segment starts clean.
		nextRanges.splice(insertIndex + 1, 0, {
			freebies: [],
			discount: { type: "none", amount: 0 },
		});
		onChange({ ...discounts, boundaries: nextBoundaries, ranges: nextRanges });
	};

	const removePoint = (index) => {
		const nextBoundaries = boundaries.filter((b, i) => i !== index);
		const nextRanges = [...ranges];
		// Merging: drop the range that started at this boundary.
		nextRanges.splice(index + 1, 1);
		onChange({ ...discounts, boundaries: nextBoundaries, ranges: nextRanges });
	};

	const updateRange = (index, updates) => {
		onChange({
			...discounts,
			ranges: ranges.map((range, i) => (i === index ? { ...range, ...updates } : range)),
		});
	};

	return (
		<div className="arraysubs-box-discounts">
			<div className="arraysubs-box-section__head">
				<h3 className="arraysubs-box-section__title">
					{__("Discounts & Freebies", "arraysubs")}
				</h3>
				<p className="arraysubs-box-section__desc">
					{__(
						"Discounts, freebies, and range points are optional. Continue without changes to skip them. Add points only when you want different offers for different box values or item counts.",
						"arraysubs"
					)}
				</p>
			</div>

			<div className="arraysubs-box-alert">
				<Info size={16} className="arraysubs-box-alert__icon" />
				<div className="arraysubs-box-alert__body">
					<strong>{__("How ranges are matched", "arraysubs")}</strong>
					<ul>
						<li>
							{__(
								"Ranges start at 0 and each one runs up to the next point: with points at 40 and 60 you get 0 – 40, 40 – 60, and 60 and above. A box lands in the range its value is greater than or equal to.",
								"arraysubs"
							)}
						</li>
						<li>
							{__(
								"The last range is open-ended. If the picker covers up to 100 and the last point is 60, a customer whose box comes to 300 still lands in that final range.",
								"arraysubs"
							)}
						</li>
						<li>
							{__(
								"Only one range ever applies — the one the box falls into. Its discount and its freebies are given together, and ranges never stack.",
								"arraysubs"
							)}
						</li>
						<li>
							{__(
								"The maximum below only sets how far the picker is drawn. It never caps what a customer may put in the box — per-element limits on the Box Steps screen do that.",
								"arraysubs"
							)}
						</li>
					</ul>
				</div>
			</div>

			<div className="arraysubs-box-field-grid arraysubs-box-field-grid--discount-controls">
				<label className="arraysubs-box-field-item">
					<span>{__("Ranges Based On", "arraysubs")}</span>
					<select value={discounts.basis} onChange={(event) => setBasis(event.target.value)}>
						<option value="total_value">{__("Total Value", "arraysubs")}</option>
						<option value="total_count">{__("Total Count", "arraysubs")}</option>
					</select>
					<small>
						{isCount
							? __("Ranges apply to the number of items in the box.", "arraysubs")
							: __("Ranges apply to the box subtotal before discount.", "arraysubs")}
					</small>
				</label>
				<label className="arraysubs-box-field-item">
					<span>
						{isCount
							? __("Max items to configure", "arraysubs")
							: __("Max amount to configure", "arraysubs")}
					</span>
					<input
						type="number"
						min={isCount ? 1 : 0.01}
						step={isCount ? 1 : 0.01}
						value={discounts.max_value || ""}
						placeholder={sprintf(
							/* translators: %s: automatically calculated range picker maximum. */
							__("Automatic (%s)", "arraysubs"),
							String(scaleMax)
						)}
						onChange={(event) => setMaxValue(event.target.value)}
						onBlur={(event) => normalizeMaxValue(event.target.value)}
					/>
					<small>
						{isCount
							? __(
									"Optional. Leave empty to size the picker automatically. This is the drawing scale only, not an item limit \u2014 lowering it pulls any point above it back to the edge.",
									"arraysubs"
							  )
							: __(
									"Optional. Leave empty to size the picker automatically. This is the drawing scale only, not a spending limit \u2014 lowering it pulls any point above it back to the edge.",
									"arraysubs"
							  )}
					</small>
				</label>
				<div className="arraysubs-box-field-item arraysubs-box-field-item--actions">
					<span className="arraysubs-box-field-item__control-spacer" aria-hidden="true">
						{"\u00a0"}
					</span>
					<button
						type="button"
						className="button"
						onClick={addPoint}
						disabled={boundaries.length >= 10}
					>
						<Plus size={14} />
						{__("Add Range Point", "arraysubs")}
					</button>
					<small>
						{boundaries.length >= 10
							? __("Maximum of 10 range points reached.", "arraysubs")
							: sprintf(
									/* translators: %d: number of range points already placed. */
									__(
										"Splits the widest gap in two. %d of 10 points placed \u2014 each point starts a new range.",
										"arraysubs"
									),
									boundaries.length
							  )}
					</small>
				</div>
			</div>

			<RangeSlider
				boundaries={boundaries}
				onChange={setBoundaries}
				scaleMax={scaleMax}
				isCount={isCount}
				currencySymbol={currencySymbol}
			/>

			<div className="arraysubs-box-ranges">
				{ranges.map((range, index) => (
					// eslint-disable-next-line react/no-array-index-key
					<div className="arraysubs-box-range-card" key={index}>
						<div className="arraysubs-box-range-card__header">
							<span
								className="arraysubs-box-range-card__swatch"
								style={{ background: rangeColor(index) }}
							/>
							<strong>{rangeBoundsLabel(index)}</strong>
							{index > 0 && (
								<span className="arraysubs-box-range-card__point">
									<label>
										{__("From", "arraysubs")}
										<input
											type="number"
											min={isCount ? 1 : 0.01}
											max={scaleMax}
											step={isCount ? 1 : 0.01}
											title={__(
												"Where this range starts. Committed when you press Enter or leave the field, and kept between its neighbouring points.",
												"arraysubs"
											)}
											value={
												drafts[index - 1] !== undefined
													? drafts[index - 1]
													: boundaries[index - 1]
											}
											onChange={(event) =>
												setDrafts((prev) => ({ ...prev, [index - 1]: event.target.value }))
											}
											onKeyDown={(event) => {
												if (event.key === "Enter") {
													event.currentTarget.blur();
												}
											}}
											onBlur={(event) => {
												const bi = index - 1;
												setDrafts((prev) => {
													const nextDraft = { ...prev };
													delete nextDraft[bi];
													return nextDraft;
												});
												const raw = event.target.value;
												if (raw === "") {
													return; // no-op: keep the existing boundary
												}
												const step = isCount ? 1 : 0.01;
												let value = Number(raw);
												if (Number.isNaN(value)) {
													return;
												}
												// Clamp strictly between neighbors so a single edit can
												// never collide with or leapfrog adjacent points.
												const lower = bi > 0 ? boundaries[bi - 1] + step : step;
												const upper =
													bi < boundaries.length - 1 ? boundaries[bi + 1] - step : scaleMax;
												value = Math.min(Math.max(value, lower), Math.max(lower, upper));
												value = isCount ? Math.round(value) : Math.round(value * 100) / 100;
												if (value === boundaries[bi]) {
													return;
												}
												const next = [...boundaries];
												next[bi] = value;
												setBoundaries(next);
											}}
										/>
									</label>
									<button
										type="button"
										className="button arraysubs-box-icon-btn arraysubs-box-icon-btn--danger"
										onClick={() => removePoint(index - 1)}
										aria-label={__("Remove range point", "arraysubs")}
										title={__(
											"Remove this point. Its range merges into the one above it and its discount and freebies are dropped.",
											"arraysubs"
										)}
									>
										<Trash2 size={13} />
									</button>
								</span>
							)}
						</div>

						<div className="arraysubs-box-field-grid">
							<div className="arraysubs-box-field-item arraysubs-box-field-item--wide">
								<span>{__("Freebies", "arraysubs")}</span>
								<AjaxSelect
									value={range.freebies}
									onChange={(freebies) => updateRange(index, { freebies })}
									endpoint="products"
									multiple
									placeholder={__("Search freebie products…", "arraysubs")}
								/>
								<small>
									{__(
										"Added to the order free when the box lands in this range. Freebies are not tied to the box billing cycle, so any purchasable product can be used.",
										"arraysubs"
									)}
								</small>
							</div>
							<label className="arraysubs-box-field-item">
								<span>{__("Discount", "arraysubs")}</span>
								<select
									value={range.discount.type}
									onChange={(event) =>
										updateRange(index, {
											discount: { ...range.discount, type: event.target.value },
										})
									}
								>
									<option value="none">{__("No discount", "arraysubs")}</option>
									<option value="fixed">{__("Fixed amount", "arraysubs")}</option>
									<option value="percent">{__("Percentage", "arraysubs")}</option>
								</select>
								<small>
									{__(
										"Taken off the box subtotal for this range only.",
										"arraysubs"
									)}
								</small>
							</label>
							{range.discount.type !== "none" && (
								<label className="arraysubs-box-field-item">
									<span>
										{range.discount.type === "percent"
											? __("Percent Off", "arraysubs")
											: sprintf(
													/* translators: %s: currency symbol. */
													__("Amount Off (%s)", "arraysubs"),
													currencySymbol
											  )}
									</span>
									<input
										type="number"
										min={0}
										max={range.discount.type === "percent" ? 100 : undefined}
										step="0.01"
										value={range.discount.amount}
										onChange={(event) => {
											let amount = Number(event.target.value);
											if (Number.isNaN(amount) || amount < 0) {
												amount = 0;
											}
											if (range.discount.type === "percent") {
												amount = Math.min(amount, 100);
											}
											updateRange(index, {
												discount: { ...range.discount, amount },
											});
										}}
									/>
									<small>
										{range.discount.type === "percent"
											? __(
													"Capped at 100%. Leave at 0 to give this range no discount.",
													"arraysubs"
											  )
											: __(
													"Never takes the box below zero. Leave at 0 to give this range no discount.",
													"arraysubs"
											  )}
									</small>
								</label>
							)}
						</div>
					</div>
				))}
			</div>

			<div className="arraysubs-box-range-summary">
				<div className="arraysubs-box-range-summary__title">
					{__("Range Summary", "arraysubs")}
				</div>
				<table className="arraysubs-box-summary-table">
					<thead>
						<tr>
							<th>{__("Range", "arraysubs")}</th>
							<th>{__("Discount", "arraysubs")}</th>
							<th>{__("Freebies", "arraysubs")}</th>
						</tr>
					</thead>
					<tbody>
						{ranges.map((range, index) => (
							// eslint-disable-next-line react/no-array-index-key
							<tr key={index}>
								<td>
									<span className="arraysubs-box-range-summary__range-label">
										<span
											className="arraysubs-box-range-card__swatch"
											style={{ background: rangeColor(index) }}
										/>
										<span>{rangeBoundsLabel(index)}</span>
									</span>
								</td>
								<td>
									{range.discount.type === "none" && "—"}
									{range.discount.type === "percent" && `${range.discount.amount}%`}
									{range.discount.type === "fixed" &&
										`${currencySymbol}${Number(range.discount.amount).toFixed(2)}`}
								</td>
								<td>
									{range.freebies.length === 0
										? "—"
										: sprintf(
												/* translators: %d: number of freebie products. */
												__("%d product(s)", "arraysubs"),
												range.freebies.length
										  )}
								</td>
							</tr>
						))}
					</tbody>
				</table>
			</div>
		</div>
	);
};

export default DiscountBuilder;
