using Content.Server.Atmos;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Botany.Components;
using Content.Server.Chemistry.Containers.EntitySystems;
using Content.Server.Fluids.Components;
using Content.Server.Ghost.Roles.Components;
using Content.Server.Kitchen.Components;
using Content.Server.Popups;
using Content.Shared.Atmos;
using Content.Shared.Botany;
using Content.Shared.Burial.Components;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Coordinates.Helpers;
using Content.Shared.Examine;
using Content.Shared.FixedPoint;
using Content.Shared.Hands.Components;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction;
using Content.Shared.Popups;
using Content.Shared.Random;
using Content.Shared.Tag;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Timing;

namespace Content.Server.Botany.Systems;

public sealed class PlantHolderSystem : EntitySystem
{
    [Dependency] private readonly AtmosphereSystem _atmosphere = default!;
    [Dependency] private readonly BotanySystem _botany = default!;
    [Dependency] private readonly IPrototypeManager _prototype = default!;
    [Dependency] private readonly MutationSystem _mutation = default!;
    [Dependency] private readonly AppearanceSystem _appearance = default!;
    [Dependency] private readonly SharedAudioSystem _audio = default!;
    [Dependency] private readonly PopupSystem _popup = default!;
    [Dependency] private readonly IGameTiming _gameTiming = default!;
    [Dependency] private readonly SharedPointLightSystem _pointLight = default!;
    [Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!;
    [Dependency] private readonly TagSystem _tagSystem = default!;
    [Dependency] private readonly RandomHelperSystem _randomHelper = default!;
    [Dependency] private readonly IRobustRandom _random = default!;


    public const float HydroponicsSpeedMultiplier = 1f;
    public const float HydroponicsConsumptionMultiplier = 2f;

    public override void Initialize()
    {
        base.Initialize();
        SubscribeLocalEvent<PlantHolderComponent, ExaminedEvent>(OnExamine);
        SubscribeLocalEvent<PlantHolderComponent, InteractUsingEvent>(OnInteractUsing);
        SubscribeLocalEvent<PlantHolderComponent, InteractHandEvent>(OnInteractHand);
    }

    public override void Update(float frameTime)
    {
        base.Update(frameTime);

        var query = EntityQueryEnumerator<PlantHolderComponent>();
        while (query.MoveNext(out var uid, out var plantHolder))
        {
            if (plantHolder.NextUpdate > _gameTiming.CurTime)
                continue;
            plantHolder.NextUpdate = _gameTiming.CurTime + plantHolder.UpdateDelay;

            Update(uid, plantHolder);
        }
    }

    private int GetCurrentGrowthStage(Entity<PlantHolderComponent> entity)
    {
        var (uid, component) = entity;

        if (component.Seed == null)
            return 0;

        var result = Math.Max(1, (int) (component.Age * component.Seed.GrowthStages / component.Seed.Maturation));
        return result;
    }

    private void OnExamine(Entity<PlantHolderComponent> entity, ref ExaminedEvent args)
    {
        if (!args.IsInDetailsRange)
            return;

        var (_, component) = entity;

        using (args.PushGroup(nameof(PlantHolderComponent)))
        {
            if (component.Seed == null)
            {
                args.PushMarkup(Loc.GetString("plant-holder-component-nothing-planted-message"));
            }
            else if (!component.Dead)
            {
                var displayName = Loc.GetString(component.Seed.DisplayName);
                args.PushMarkup(Loc.GetString("plant-holder-component-something-already-growing-message",
                    ("seedName", displayName),
                    ("toBeForm", displayName.EndsWith('s') ? "are" : "is")));

                if (component.Health <= component.Seed.Endurance / 2)
                {
                    args.PushMarkup(Loc.GetString(
                        "plant-holder-component-something-already-growing-low-health-message",
                        ("healthState",
                            Loc.GetString(component.Age > component.Seed.Lifespan
                                ? "plant-holder-component-plant-old-adjective"
                                : "plant-holder-component-plant-unhealthy-adjective"))));
                }
            }
            else
            {
                args.PushMarkup(Loc.GetString("plant-holder-component-dead-plant-matter-message"));
            }

            if (component.WeedLevel >= 5)
                args.PushMarkup(Loc.GetString("plant-holder-component-weed-high-level-message"));

            if (component.PestLevel >= 5)
                args.PushMarkup(Loc.GetString("plant-holder-component-pest-high-level-message"));

            args.PushMarkup(Loc.GetString($"plant-holder-component-water-level-message",
                ("waterLevel", (int) component.WaterLevel)));
            args.PushMarkup(Loc.GetString($"plant-holder-component-nutrient-level-message",
                ("nutritionLevel", (int) component.NutritionLevel)));

            if (component.DrawWarnings)
            {
                if (component.Toxins > 40f)
                    args.PushMarkup(Loc.GetString("plant-holder-component-toxins-high-warning"));

                if (component.ImproperLight)
                    args.PushMarkup(Loc.GetString("plant-holder-component-light-improper-warning"));

                if (component.ImproperHeat)
                    args.PushMarkup(Loc.GetString("plant-holder-component-heat-improper-warning"));

                if (component.ImproperPressure)
                    args.PushMarkup(Loc.GetString("plant-holder-component-pressure-improper-warning"));

                if (component.MissingGas > 0)
                    args.PushMarkup(Loc.GetString("plant-holder-component-gas-missing-warning"));
            }
        }
    }

    private void OnInteractUsing(Entity<PlantHolderComponent> entity, ref InteractUsingEvent args)
    {
        var (uid, component) = entity;

        if (TryComp(args.Used, out SeedComponent? seeds))
        {
            if (component.Seed == null)
            {
                if (!_botany.TryGetSeed(seeds, out var seed))
                    return;

                var name = Loc.GetString(seed.Name);
                var noun = Loc.GetString(seed.Noun);
                _popup.PopupCursor(Loc.GetString("plant-holder-component-plant-success-message",
                    ("seedName", name),
                    ("seedNoun", noun)), args.User, PopupType.Medium);

                component.Seed = seed;
                component.Dead = false;
                component.Age = 1;
                if (seeds.HealthOverride != null)
                {
                    component.Health = seeds.HealthOverride.Value;
                }
                else
                {
                    component.Health = component.Seed.Endurance;
                }
                component.LastCycle = _gameTiming.CurTime;

                QueueDel(args.Used);

                CheckLevelSanity(uid, component);
                UpdateSprite(uid, component);

                return;
            }

            _popup.PopupCursor(Loc.GetString("plant-holder-component-already-seeded-message",
                ("name", Comp<MetaDataComponent>(uid).EntityName)), args.User, PopupType.Medium);
            return;
        }

        if (_tagSystem.HasTag(args.Used, "Hoe"))
        {
            if (component.WeedLevel > 0)
            {
                _popup.PopupCursor(Loc.GetString("plant-holder-component-remove-weeds-message",
                    ("name", Comp<MetaDataComponent>(uid).EntityName)), args.User, PopupType.Medium);
                _popup.PopupEntity(Loc.GetString("plant-holder-component-remove-weeds-others-message",
                    ("otherName", Comp<MetaDataComponent>(args.User).EntityName)), uid, Filter.PvsExcept(args.User), true);
                component.WeedLevel = 0;
                UpdateSprite(uid, component);
            }
            else
            {
                _popup.PopupCursor(Loc.GetString("plant-holder-component-no-weeds-message"), args.User);
            }

            return;
        }

        if (HasComp<ShovelComponent>(args.Used))
        {
            if (component.Seed != null)
            {
                _popup.PopupCursor(Loc.GetString("plant-holder-component-remove-plant-message",
                    ("name", Comp<MetaDataComponent>(uid).EntityName)), args.User, PopupType.Medium);
                _popup.PopupEntity(Loc.GetString("plant-holder-component-remove-plant-others-message",
                    ("name", Comp<MetaDataComponent>(args.User).EntityName)), uid, Filter.PvsExcept(args.User), true);
                RemovePlant(uid, component);
            }
            else
            {
                _popup.PopupCursor(Loc.GetString("plant-holder-component-no-plant-message",
                    ("name", Comp<MetaDataComponent>(uid).EntityName)), args.User);
            }

            return;
        }

        if (_solutionContainerSystem.TryGetDrainableSolution(args.Used, out var solution, out _)
            && _solutionContainerSystem.ResolveSolution(uid, component.SoilSolutionName, ref component.SoilSolution)
            && TryComp(args.Used, out SprayComponent? spray))
        {
            var amount = FixedPoint2.New(1);

            var targetEntity = uid;
            var solutionEntity = args.Used;

            _audio.PlayPvs(spray.SpraySound, args.Used, AudioParams.Default.WithVariation(0.125f));

            var split = _solutionContainerSystem.Drain(solutionEntity, solution.Value, amount);

            if (split.Volume == 0)
            {
                _popup.PopupCursor(Loc.GetString("plant-holder-component-no-plant-message",
                    ("owner", args.Used)), args.User);
                return;
            }

            _popup.PopupCursor(Loc.GetString("plant-holder-component-spray-message",
                ("owner", uid),
                ("amount", split.Volume)), args.User, PopupType.Medium);

            _solutionContainerSystem.TryAddSolution(component.SoilSolution.Value, split);

            ForceUpdateByExternalCause(uid, component);

            return;
        }

        if (_tagSystem.HasTag(args.Used, "PlantSampleTaker"))
        {
            if (component.Seed == null)
            {
                _popup.PopupCursor(Loc.GetString("plant-holder-component-nothing-to-sample-message"), args.User);
                return;
            }

            if (component.Sampled)
            {
                _popup.PopupCursor(Loc.GetString("plant-holder-component-already-sampled-message"), args.User);
                return;
            }

            if (component.Dead)
            {
                _popup.PopupCursor(Loc.GetString("plant-holder-component-dead-plant-message"), args.User);
                return;
            }

            if (GetCurrentGrowthStage(entity) <= 1)
            {
                _popup.PopupCursor(Loc.GetString("plant-holder-component-early-sample-message"), args.User);
                return;
            }

            component.Health -= (_random.Next(3, 5) * 10);

            float? healthOverride;
            if (component.Harvest)
            {
                healthOverride = null;
            }
            else
            {
                healthOverride = component.Health;
            }
            component.Seed.Unique = false;
            var seed = _botany.SpawnSeedPacket(component.Seed, Transform(args.User).Coordinates, args.User, healthOverride);
            _randomHelper.RandomOffset(seed, 0.25f);
            var displayName = Loc.GetString(component.Seed.DisplayName);
            _popup.PopupCursor(Loc.GetString("plant-holder-component-take-sample-message",
                ("seedName", displayName)), args.User);

            if (component.Seed != null && component.Seed.CanScream)
            {
                _audio.PlayPvs(component.Seed.ScreamSound, uid, AudioParams.Default.WithVolume(-2));
            }

            if (_random.Prob(0.3f))
                component.Sampled = true;

            // Just in case.
            CheckLevelSanity(uid, component);
            ForceUpdateByExternalCause(uid, component);

            return;
        }

        if (HasComp<SharpComponent>(args.Used))
            DoHarvest(uid, args.User, component);

        if (TryComp<ProduceComponent>(args.Used, out var produce))
        {
            _popup.PopupCursor(Loc.GetString("plant-holder-component-compost-message",
                ("owner", uid),
                ("usingItem", args.Used)), args.User, PopupType.Medium);
            _popup.PopupEntity(Loc.GetString("plant-holder-component-compost-others-message",
                ("user", Identity.Entity(args.User, EntityManager)),
                ("usingItem", args.Used),
                ("owner", uid)), uid, Filter.PvsExcept(args.User), true);

            if (_solutionContainerSystem.TryGetSolution(args.Used, produce.SolutionName, out var soln2, out var solution2))
            {
                if (_solutionContainerSystem.ResolveSolution(uid, component.SoilSolutionName, ref component.SoilSolution, out var solution1))
                {
                    // We try to fit as much of the composted plant's contained solution into the hydroponics tray as we can,
                    // since the plant will be consumed anyway.

                    var fillAmount = FixedPoint2.Min(solution2.Volume, solution1.AvailableVolume);
                    _solutionContainerSystem.TryAddSolution(component.SoilSolution.Value, _solutionContainerSystem.SplitSolution(soln2.Value, fillAmount));

                    ForceUpdateByExternalCause(uid, component);
                }
            }
            var seed = produce.Seed;
            if (seed != null)
            {
                var nutrientBonus = seed.Potency / 2.5f;
                AdjustNutrient(uid, nutrientBonus, component);
            }
            QueueDel(args.Used);
        }
    }

    private void OnInteractHand(Entity<PlantHolderComponent> entity, ref InteractHandEvent args)
    {
        DoHarvest(entity, args.User, entity.Comp);
    }

    public void WeedInvasion()
    {
        // TODO
    }


    public void Update(EntityUid uid, PlantHolderComponent? component = null)
    {
        if (!Resolve(uid, ref component))
            return;

        UpdateReagents(uid, component);

        var curTime = _gameTiming.CurTime;

        if (component.ForceUpdate)
            component.ForceUpdate = false;
        else if (curTime < (component.LastCycle + component.CycleDelay))
        {
            if (component.UpdateSpriteAfterUpdate)
                UpdateSprite(uid, component);
            return;
        }

        component.LastCycle = curTime;

        // Process mutations
        if (component.MutationLevel > 0)
        {
            Mutate(uid, Math.Min(component.MutationLevel, 25), component);
            component.UpdateSpriteAfterUpdate = true;
            component.MutationLevel = 0;
        }

        // Weeds like water and nutrients! They may appear even if there's not a seed planted.
        if (component.WaterLevel > 10 && component.NutritionLevel > 5)
        {
            var chance = 0f;
            if (component.Seed == null)
                chance = 0.05f;
            else if (component.Seed.TurnIntoKudzu)
                chance = 1f;
            else
                chance = 0.01f;

            if (_random.Prob(chance))
                component.WeedLevel += 1 + HydroponicsSpeedMultiplier * component.WeedCoefficient;

            if (component.DrawWarnings)
                component.UpdateSpriteAfterUpdate = true;
        }

        if (component.Seed != null && component.Seed.TurnIntoKudzu
            && component.WeedLevel >= component.Seed.WeedHighLevelThreshold)
        {
            Spawn(component.Seed.KudzuPrototype, Transform(uid).Coordinates.SnapToGrid(EntityManager));
            component.Seed.TurnIntoKudzu = false;
            component.Health = 0;
        }

        // There's a chance for a weed explosion to happen if weeds take over.
        // Plants that are themselves weeds (WeedTolerance > 8) are unaffected.
        if (component.WeedLevel >= 10 && _random.Prob(0.1f))
        {
            if (component.Seed == null || component.WeedLevel >= component.Seed.WeedTolerance + 2)
                WeedInvasion();
        }

        // If we have no seed planted, or the plant is dead, stop processing here.
        if (component.Seed == null || component.Dead)
        {
            if (component.UpdateSpriteAfterUpdate)
                UpdateSprite(uid, component);

            return;
        }

        // There's a small chance the pest population increases.
        // Can only happen when there's a live seed planted.
        if (_random.Prob(0.01f))
        {
            component.PestLevel += 0.5f * HydroponicsSpeedMultiplier;
            if (component.DrawWarnings)
                component.UpdateSpriteAfterUpdate = true;
        }

        // Advance plant age here.
        if (component.SkipAging > 0)
            component.SkipAging--;
        else
        {
            if (_random.Prob(0.8f))
                component.Age += (int) (1 * HydroponicsSpeedMultiplier);

            component.UpdateSpriteAfterUpdate = true;
        }

        // Nutrient consumption.
        if (component.Seed.NutrientConsumption > 0 && component.NutritionLevel > 0 && _random.Prob(0.75f))
        {
            component.NutritionLevel -= MathF.Max(0f, component.Seed.NutrientConsumption * HydroponicsSpeedMultiplier);
            if (component.DrawWarnings)
                component.UpdateSpriteAfterUpdate = true;
        }

        // Water consumption.
        if (component.Seed.WaterConsumption > 0 && component.WaterLevel > 0 && _random.Prob(0.75f))
        {
            component.WaterLevel -= MathF.Max(0f,
                component.Seed.WaterConsumption * HydroponicsConsumptionMultiplier * HydroponicsSpeedMultiplier);
            if (component.DrawWarnings)
                component.UpdateSpriteAfterUpdate = true;
        }

        var healthMod = _random.Next(1, 3) * HydroponicsSpeedMultiplier;

        // Make sure genetics are viable.
        if (!component.Seed.Viable)
        {
            AffectGrowth(uid, -1, component);
            component.Health -= 6 * healthMod;
        }

        // Prevents the plant from aging when lacking resources.
        // Limits the effect on aging so that when resources are added, the plant starts growing in a reasonable amount of time.
        if (component.SkipAging < 10)
        {
            // Make sure the plant is not starving.
            if (component.NutritionLevel > 5)
            {
                component.Health += Convert.ToInt32(_random.Prob(0.35f)) * healthMod;
            }
            else
            {
                AffectGrowth(uid, -1, component);
                component.Health -= healthMod;
            }

            // Make sure the plant is not thirsty.
            if (component.WaterLevel > 10)
            {
                component.Health += Convert.ToInt32(_random.Prob(0.35f)) * healthMod;
            }
            else
            {
                AffectGrowth(uid, -1, component);
                component.Health -= healthMod;
            }

            if (component.DrawWarnings)
                component.UpdateSpriteAfterUpdate = true;
        }

        var environment = _atmosphere.GetContainingMixture(uid, true, true) ?? GasMixture.SpaceGas;

        if (component.Seed.ConsumeGasses.Count > 0)
        {
            component.MissingGas = 0;

            foreach (var (gas, amount) in component.Seed.ConsumeGasses)
            {
                if (environment.GetMoles(gas) < amount)
                {
                    component.MissingGas++;
                    continue;
                }

                environment.AdjustMoles(gas, -amount);
            }

            if (component.MissingGas > 0)
            {
                component.Health -= component.MissingGas * HydroponicsSpeedMultiplier;
                if (component.DrawWarnings)
                    component.UpdateSpriteAfterUpdate = true;
            }
        }

        // SeedPrototype pressure resistance.
        var pressure = environment.Pressure;
        if (pressure < component.Seed.LowPressureTolerance || pressure > component.Seed.HighPressureTolerance)
        {
            component.Health -= healthMod;
            component.ImproperPressure = true;
            if (component.DrawWarnings)
                component.UpdateSpriteAfterUpdate = true;
        }
        else
        {
            component.ImproperPressure = false;
        }

        // SeedPrototype ideal temperature.
        if (MathF.Abs(environment.Temperature - component.Seed.IdealHeat) > component.Seed.HeatTolerance)
        {
            component.Health -= healthMod;
            component.ImproperHeat = true;
            if (component.DrawWarnings)
                component.UpdateSpriteAfterUpdate = true;
        }
        else
        {
            component.ImproperHeat = false;
        }

        // Gas production.
        var exudeCount = component.Seed.ExudeGasses.Count;
        if (exudeCount > 0)
        {
            foreach (var (gas, amount) in component.Seed.ExudeGasses)
            {
                environment.AdjustMoles(gas,
                    MathF.Max(1f, MathF.Round(amount * MathF.Round(component.Seed.Potency) / exudeCount)));
            }
        }

        // Toxin levels beyond the plant's tolerance cause damage.
        // They are, however, slowly reduced over time.
        if (component.Toxins > 0)
        {
            var toxinUptake = MathF.Max(1, MathF.Round(component.Toxins / 10f));
            if (component.Toxins > component.Seed.ToxinsTolerance)
            {
                component.Health -= toxinUptake;
            }

            component.Toxins -= toxinUptake;
            if (component.DrawWarnings)
                component.UpdateSpriteAfterUpdate = true;
        }

        // Weed levels.
        if (component.PestLevel > 0)
        {
            // TODO: Carnivorous plants?
            if (component.PestLevel > component.Seed.PestTolerance)
            {
                component.Health -= HydroponicsSpeedMultiplier;
            }

            if (component.DrawWarnings)
                component.UpdateSpriteAfterUpdate = true;
        }

        // Weed levels.
        if (component.WeedLevel > 0)
        {
            // TODO: Parasitic plants.
            if (component.WeedLevel >= component.Seed.WeedTolerance)
            {
                component.Health -= HydroponicsSpeedMultiplier;
            }

            if (component.DrawWarnings)
                component.UpdateSpriteAfterUpdate = true;
        }

        if (component.Age > component.Seed.Lifespan)
        {
            component.Health -= _random.Next(3, 5) * HydroponicsSpeedMultiplier;
            if (component.DrawWarnings)
                component.UpdateSpriteAfterUpdate = true;
        }
        else if (component.Age < 0) // Revert back to seed packet!
        {
            // will put it in the trays hands if it has any, please do not try doing this
            _botany.SpawnSeedPacket(component.Seed, Transform(uid).Coordinates, uid);
            RemovePlant(uid, component);
            component.ForceUpdate = true;
            Update(uid, component);
            return;
        }

        CheckHealth(uid, component);

        if (component.Harvest && component.Seed.HarvestRepeat == HarvestType.SelfHarvest)
            AutoHarvest(uid, component);

        // If enough time has passed since the plant was harvested, we're ready to harvest again!
        if (!component.Dead && component.Seed.ProductPrototypes.Count > 0)
        {
            if (component.Age > component.Seed.Production)
            {
                if (component.Age - component.LastProduce > component.Seed.Production && !component.Harvest)
                {
                    component.Harvest = true;
                    component.LastProduce = component.Age;
                }
            }
            else
            {
                if (component.Harvest)
                {
                    component.Harvest = false;
                    component.LastProduce = component.Age;
                }
            }
        }

        CheckLevelSanity(uid, component);

        if (component.Seed.Sentient)
        {
            var ghostRole = EnsureComp<GhostRoleComponent>(uid);
            EnsureComp<GhostTakeoverAvailableComponent>(uid);
            ghostRole.RoleName = MetaData(uid).EntityName;
            ghostRole.RoleDescription = Loc.GetString("station-event-random-sentience-role-description", ("name", ghostRole.RoleName));
        }

        if (component.UpdateSpriteAfterUpdate)
            UpdateSprite(uid, component);
    }

    //TODO: kill this bullshit
    public void CheckLevelSanity(EntityUid uid, PlantHolderComponent? component = null)
    {
        if (!Resolve(uid, ref component))
            return;

        if (component.Seed != null)
            component.Health = MathHelper.Clamp(component.Health, 0, component.Seed.Endurance);
        else
        {
            component.Health = 0f;
            component.Dead = false;
        }

        component.MutationLevel = MathHelper.Clamp(component.MutationLevel, 0f, 100f);
        component.NutritionLevel = MathHelper.Clamp(component.NutritionLevel, 0f, 100f);
        component.WaterLevel = MathHelper.Clamp(component.WaterLevel, 0f, 100f);
        component.PestLevel = MathHelper.Clamp(component.PestLevel, 0f, 10f);
        component.WeedLevel = MathHelper.Clamp(component.WeedLevel, 0f, 10f);
        component.Toxins = MathHelper.Clamp(component.Toxins, 0f, 100f);
        component.YieldMod = MathHelper.Clamp(component.YieldMod, 0, 2);
        component.MutationMod = MathHelper.Clamp(component.MutationMod, 0f, 3f);
    }

    public bool DoHarvest(EntityUid plantholder, EntityUid user, PlantHolderComponent? component = null)
    {
        if (!Resolve(plantholder, ref component))
            return false;

        if (component.Seed == null || Deleted(user))
            return false;


        if (component.Harvest && !component.Dead)
        {
            if (TryComp<HandsComponent>(user, out var hands))
            {
                if (!_botany.CanHarvest(component.Seed, hands.ActiveHandEntity))
                    return false;
            }
            else if (!_botany.CanHarvest(component.Seed))
            {
                return false;
            }

            _botany.Harvest(component.Seed, user, component.YieldMod);
            AfterHarvest(plantholder, component);
            return true;
        }

        if (!component.Dead)
            return false;

        RemovePlant(plantholder, component);
        AfterHarvest(plantholder, component);
        return true;
    }

    public void AutoHarvest(EntityUid uid, PlantHolderComponent? component = null)
    {
        if (!Resolve(uid, ref component))
            return;

        if (component.Seed == null || !component.Harvest)
            return;

        _botany.AutoHarvest(component.Seed, Transform(uid).Coordinates);
        AfterHarvest(uid, component);
    }

    private void AfterHarvest(EntityUid uid, PlantHolderComponent? component = null)
    {
        if (!Resolve(uid, ref component))
            return;

        component.Harvest = false;
        component.LastProduce = component.Age;

        if (component.Seed != null && component.Seed.CanScream)
            _audio.PlayPvs(component.Seed.ScreamSound, uid, AudioParams.Default.WithVolume(-2));

        if (component.Seed?.HarvestRepeat == HarvestType.NoRepeat)
            RemovePlant(uid, component);

        CheckLevelSanity(uid, component);
        UpdateSprite(uid, component);
    }

    public void CheckHealth(EntityUid uid, PlantHolderComponent? component = null)
    {
        if (!Resolve(uid, ref component))
            return;

        if (component.Health <= 0)
        {
            Die(uid, component);
        }
    }

    public void Die(EntityUid uid, PlantHolderComponent? component = null)
    {
        if (!Resolve(uid, ref component))
            return;

        component.Dead = true;
        component.Harvest = false;
        component.MutationLevel = 0;
        component.YieldMod = 1;
        component.MutationMod = 1;
        component.ImproperLight = false;
        component.ImproperHeat = false;
        component.ImproperPressure = false;
        component.WeedLevel += 1 * HydroponicsSpeedMultiplier;
        component.PestLevel = 0;
        UpdateSprite(uid, component);
    }

    public void RemovePlant(EntityUid uid, PlantHolderComponent? component = null)
    {
        if (!Resolve(uid, ref component))
            return;

        component.YieldMod = 1;
        component.MutationMod = 1;
        component.PestLevel = 0;
        component.Seed = null;
        component.Dead = false;
        component.Age = 0;
        component.LastProduce = 0;
        component.Sampled = false;
        component.Harvest = false;
        component.ImproperLight = false;
        component.ImproperPressure = false;
        component.ImproperHeat = false;

        UpdateSprite(uid, component);
    }

    public void AffectGrowth(EntityUid uid, int amount, PlantHolderComponent? component = null)
    {
        if (!Resolve(uid, ref component))
            return;

        if (component.Seed == null)
            return;

        if (amount > 0)
        {
            if (component.Age < component.Seed.Maturation)
                component.Age += amount;
            else if (!component.Harvest && component.Seed.Yield <= 0f)
                component.LastProduce -= amount;
        }
        else
        {
            if (component.Age < component.Seed.Maturation)
                component.SkipAging++;
            else if (!component.Harvest && component.Seed.Yield <= 0f)
                component.LastProduce += amount;
        }
    }

    public void AdjustNutrient(EntityUid uid, float amount, PlantHolderComponent? component = null)
    {
        if (!Resolve(uid, ref component))
            return;

        component.NutritionLevel += amount;
    }

    public void AdjustWater(EntityUid uid, float amount, PlantHolderComponent? component = null)
    {
        if (!Resolve(uid, ref component))
            return;

        component.WaterLevel += amount;

        // Water dilutes toxins.
        if (amount > 0)
        {
            component.Toxins -= amount * 4f;
        }
    }

    public void UpdateReagents(EntityUid uid, PlantHolderComponent? component = null)
    {
        if (!Resolve(uid, ref component))
            return;

        if (!_solutionContainerSystem.ResolveSolution(uid, component.SoilSolutionName, ref component.SoilSolution, out var solution))
            return;

        if (solution.Volume > 0 && component.MutationLevel < 25)
        {
            var amt = FixedPoint2.New(1);
            foreach (var entry in _solutionContainerSystem.RemoveEachReagent(component.SoilSolution.Value, amt))
            {
                var reagentProto = _prototype.Index<ReagentPrototype>(entry.Reagent.Prototype);
                reagentProto.ReactionPlant(uid, entry, solution);
            }
        }

        CheckLevelSanity(uid, component);
    }

    private void Mutate(EntityUid uid, float severity, PlantHolderComponent? component = null)
    {
        if (!Resolve(uid, ref component))
            return;

        if (component.Seed != null)
        {
            EnsureUniqueSeed(uid, component);
            _mutation.MutateSeed(ref component.Seed, severity);
        }
    }

    public void UpdateSprite(EntityUid uid, PlantHolderComponent? component = null)
    {
        if (!Resolve(uid, ref component))
            return;

        component.UpdateSpriteAfterUpdate = false;

        if (component.Seed != null && component.Seed.Bioluminescent)
        {
            var light = EnsureComp<PointLightComponent>(uid);
            _pointLight.SetRadius(uid, component.Seed.BioluminescentRadius, light);
            _pointLight.SetColor(uid, component.Seed.BioluminescentColor, light);
            _pointLight.SetCastShadows(uid, false, light);
            Dirty(uid, light);
        }
        else
        {
            RemComp<PointLightComponent>(uid);
        }

        if (!TryComp<AppearanceComponent>(uid, out var app))
            return;

        if (component.Seed != null)
        {
            if (component.DrawWarnings)
            {
                _appearance.SetData(uid, PlantHolderVisuals.HealthLight, component.Health <= component.Seed.Endurance / 2f);
            }

            if (component.Dead)
            {
                _appearance.SetData(uid, PlantHolderVisuals.PlantRsi, component.Seed.PlantRsi.ToString(), app);
                _appearance.SetData(uid, PlantHolderVisuals.PlantState, "dead", app);
            }
            else if (component.Harvest)
            {
                _appearance.SetData(uid, PlantHolderVisuals.PlantRsi, component.Seed.PlantRsi.ToString(), app);
                _appearance.SetData(uid, PlantHolderVisuals.PlantState, "harvest", app);
            }
            else if (component.Age < component.Seed.Maturation)
            {
                var growthStage = GetCurrentGrowthStage((uid, component));

                _appearance.SetData(uid, PlantHolderVisuals.PlantRsi, component.Seed.PlantRsi.ToString(), app);
                _appearance.SetData(uid, PlantHolderVisuals.PlantState, $"stage-{growthStage}", app);
                component.LastProduce = component.Age;
            }
            else
            {
                _appearance.SetData(uid, PlantHolderVisuals.PlantRsi, component.Seed.PlantRsi.ToString(), app);
                _appearance.SetData(uid, PlantHolderVisuals.PlantState, $"stage-{component.Seed.GrowthStages}", app);
            }
        }
        else
        {
            _appearance.SetData(uid, PlantHolderVisuals.PlantState, "", app);
            _appearance.SetData(uid, PlantHolderVisuals.HealthLight, false, app);
        }

        if (!component.DrawWarnings)
            return;

        _appearance.SetData(uid, PlantHolderVisuals.WaterLight, component.WaterLevel <= 15, app);
        _appearance.SetData(uid, PlantHolderVisuals.NutritionLight, component.NutritionLevel <= 8, app);
        _appearance.SetData(uid, PlantHolderVisuals.AlertLight,
            component.WeedLevel >= 5 || component.PestLevel >= 5 || component.Toxins >= 40 || component.ImproperHeat ||
            component.ImproperLight || component.ImproperPressure || component.MissingGas > 0, app);
        _appearance.SetData(uid, PlantHolderVisuals.HarvestLight, component.Harvest, app);
    }

    /// <summary>
    ///     Check if the currently contained seed is unique. If it is not, clone it so that we have a unique seed.
    ///     Necessary to avoid modifying global seeds.
    /// </summary>
    public void EnsureUniqueSeed(EntityUid uid, PlantHolderComponent? component = null)
    {
        if (!Resolve(uid, ref component))
            return;

        if (component.Seed is { Unique: false })
            component.Seed = component.Seed.Clone();
    }

    public void ForceUpdateByExternalCause(EntityUid uid, PlantHolderComponent? component = null)
    {
        if (!Resolve(uid, ref component))
            return;

        component.SkipAging++; // We're forcing an update cycle, so one age hasn't passed.
        component.ForceUpdate = true;
        Update(uid, component);
    }
}